package com.mzh.library.service; import com.mzh.library.dao.SystemLogDao; import com.mzh.library.dao.UserAccountDao; import com.mzh.library.entity.AuthenticatedUser; import com.mzh.library.entity.Permission; import com.mzh.library.entity.Role; import com.mzh.library.entity.SystemLog; import com.mzh.library.entity.SystemLogSearchCriteria; import com.mzh.library.entity.User; import com.mzh.library.entity.UserSearchCriteria; import com.mzh.library.exception.DaoException; import com.mzh.library.service.impl.UserAccountServiceImpl; import com.mzh.library.util.PasswordHasher; import java.sql.Connection; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; public final class UserAccountServiceCheck { private static final String DENIED_MESSAGE = "您无权管理用户。"; private static final String UNAVAILABLE_MESSAGE = "用户管理服务暂时不可用,请稍后重试。"; private UserAccountServiceCheck() { } public static void main(String[] args) { Logger.getLogger(UserAccountServiceImpl.class.getName()).setLevel(Level.OFF); InMemoryUserAccountDao userDao = new InMemoryUserAccountDao(); InMemorySystemLogDao logDao = new InMemorySystemLogDao(); User adminAccount = user(1L, "admin", "System Administrator", Role.ADMINISTRATOR, true, "admin-password"); User staffAccount = user(2L, "staff", "Library Staff", Role.LIBRARIAN, true, "staff-password"); userDao.put(adminAccount); userDao.put(staffAccount); UserAccountService service = new UserAccountServiceImpl(userDao, logDao, new PermissionPolicy(), new UserAccountServiceImpl.DirectTransactionExecutor()); AuthenticatedUser admin = actor(1L, Role.ADMINISTRATOR); AuthenticatedUser reader = actor(3L, Role.READER); requireMessage(service.searchUsers(reader, new UserSearchCriteria()), DENIED_MESSAGE); User duplicate = user(0L, " admin ", "Duplicate Admin", Role.ADMINISTRATOR, true, "unused"); ServiceResult duplicateResult = service.createUser(admin, duplicate, "new-password", "127.0.0.1"); require(!duplicateResult.isSuccessful(), "duplicate username should be rejected"); require(duplicateResult.getErrors().containsKey("username"), "duplicate username should target username"); User invalid = new User(); ServiceResult invalidResult = service.createUser(admin, invalid, "", "127.0.0.1"); require(!invalidResult.isSuccessful(), "invalid user should be rejected"); require(invalidResult.getErrors().containsKey("username"), "missing username should be reported"); require(invalidResult.getErrors().containsKey("displayName"), "missing display name should be reported"); require(invalidResult.getErrors().containsKey("password"), "missing password should be reported"); User newReader = new User(); newReader.setUsername(" new.reader "); newReader.setDisplayName("New Reader"); newReader.setRole(Role.READER); newReader.setActive(true); ServiceResult created = service.createUser(admin, newReader, "reader-password", "127.0.0.1"); require(created.isSuccessful(), "administrator should create user accounts"); User storedReader = userDao.findById(created.getData()).orElseThrow(AssertionError::new); require("new.reader".equals(storedReader.getUsername()), "username should be trimmed like login"); require(!"reader-password".equals(storedReader.getPasswordHash()), "password should not be stored in plain text"); require(PasswordHasher.verify("reader-password", storedReader.getPasswordHash()), "stored password should verify"); require(logDao.logs.size() == 1, "user creation should write one system log"); require("user.create".equals(logDao.logs.get(0).getOperationType()), "creation log should use user.create"); User selfRoleChange = user(1L, "admin", "System Administrator", Role.LIBRARIAN, true, "ignored"); ServiceResult selfRoleResult = service.updateUser(admin, selfRoleChange, "", "127.0.0.1"); require(!selfRoleResult.isSuccessful(), "current administrator role change should be blocked"); require(selfRoleResult.getErrors().containsKey("role"), "self role change should target role"); User selfDeactivate = user(1L, "admin", "System Administrator", Role.ADMINISTRATOR, false, "ignored"); ServiceResult selfDeactivateResult = service.updateUser(admin, selfDeactivate, "", "127.0.0.1"); require(!selfDeactivateResult.isSuccessful(), "current administrator deactivation should be blocked"); require(selfDeactivateResult.getErrors().containsKey("active"), "self deactivation should target active state"); String originalStaffHash = staffAccount.getPasswordHash(); User updatedStaff = user(2L, "staff", "Lead Librarian", Role.LIBRARIAN, true, "ignored"); ServiceResult updated = service.updateUser(admin, updatedStaff, "", "127.0.0.1"); require(updated.isSuccessful(), "administrator should update user accounts"); require(originalStaffHash.equals(userDao.findById(2L).orElseThrow(AssertionError::new).getPasswordHash()), "blank update password should preserve existing hash"); ServiceResult reset = service.updateUser(admin, updatedStaff, "replacement-password", "127.0.0.1"); require(reset.isSuccessful(), "administrator should reset passwords"); require(PasswordHasher.verify("replacement-password", userDao.findById(2L).orElseThrow(AssertionError::new).getPasswordHash()), "replacement password should be hashed"); ServiceResult deactivated = service.deactivateUser(admin, 2L, "127.0.0.1"); require(deactivated.isSuccessful(), "administrator should deactivate other accounts"); require(!userDao.findById(2L).orElseThrow(AssertionError::new).isActive(), "deactivate action should mark account inactive"); require(logDao.logs.stream().anyMatch(log -> "user.deactivate".equals(log.getOperationType())), "deactivate should write a system log"); UserAccountService failingService = new UserAccountServiceImpl(new FailingUserAccountDao(), logDao, new PermissionPolicy(), new UserAccountServiceImpl.DirectTransactionExecutor()); requireMessage(failingService.searchUsers(admin, new UserSearchCriteria()), UNAVAILABLE_MESSAGE); } private static User user(long id, String username, String displayName, Role role, boolean active, String password) { User user = new User(); user.setId(id); user.setUsername(username); user.setDisplayName(displayName); user.setRole(role); user.setActive(active); user.setPasswordHash(PasswordHasher.hash(password)); return user; } private static AuthenticatedUser actor(long id, Role role) { return new AuthenticatedUser(id, role.getCode(), role.getDisplayName(), role, role == Role.ADMINISTRATOR ? EnumSet.allOf(Permission.class) : EnumSet.of(Permission.VIEW_CATALOG, Permission.BORROW_BOOKS)); } private static void requireMessage(ServiceResult result, String message) { require(!result.isSuccessful(), "result should be a failure"); require(message.equals(result.getMessage()), "expected message: " + message); } private static void require(boolean condition, String message) { if (!condition) { throw new AssertionError(message); } } private static final class InMemoryUserAccountDao implements UserAccountDao { private final Map users = new LinkedHashMap<>(); private long nextId = 10L; private void put(User user) { users.put(user.getId(), copy(user)); } @Override public List search(UserSearchCriteria criteria) { return users.values().stream() .filter(user -> criteria.getRoleCode().isEmpty() || user.getRole().getCode().equals(criteria.getRoleCode())) .filter(user -> criteria.getActiveValue() == null || user.isActive() == criteria.getActiveValue()) .map(this::copy) .collect(Collectors.toList()); } @Override public Optional findById(long id) { return Optional.ofNullable(users.get(id)).map(this::copy); } @Override public Optional findByUsername(String username) { return users.values().stream() .filter(user -> user.getUsername().equals(username)) .findFirst() .map(this::copy); } @Override public long create(Connection connection, User user) { long id = nextId++; User stored = copy(user); stored.setId(id); users.put(id, stored); return id; } @Override public boolean update(Connection connection, User user, boolean updatePassword) { User existing = users.get(user.getId()); if (existing == null) { return false; } existing.setDisplayName(user.getDisplayName()); existing.setRole(user.getRole()); existing.setActive(user.isActive()); if (updatePassword) { existing.setPasswordHash(user.getPasswordHash()); } return true; } private User copy(User source) { User copy = new User(); copy.setId(source.getId()); copy.setUsername(source.getUsername()); copy.setDisplayName(source.getDisplayName()); copy.setRole(source.getRole()); copy.setActive(source.isActive()); copy.setPasswordHash(source.getPasswordHash()); copy.setCreatedAt(source.getCreatedAt()); copy.setUpdatedAt(source.getUpdatedAt()); return copy; } } private static final class InMemorySystemLogDao implements SystemLogDao { private final List logs = new ArrayList<>(); @Override public List search(SystemLogSearchCriteria criteria) { return new ArrayList<>(logs); } @Override public List findOperationTypes() { return logs.stream() .map(SystemLog::getOperationType) .distinct() .collect(Collectors.toList()); } @Override public long create(Connection connection, SystemLog log) { log.setId(logs.size() + 1L); logs.add(log); return log.getId(); } } private static final class FailingUserAccountDao implements UserAccountDao { @Override public List search(UserSearchCriteria criteria) { throw new DaoException("Simulated user search failure", null); } @Override public Optional findById(long id) { throw new DaoException("Simulated user lookup failure", null); } @Override public Optional findByUsername(String username) { throw new DaoException("Simulated username lookup failure", null); } @Override public long create(Connection connection, User user) { throw new DaoException("Simulated user create failure", null); } @Override public boolean update(Connection connection, User user, boolean updatePassword) { throw new DaoException("Simulated user update failure", null); } } }