添加了swagger接口、重置密码、修改了usercontroller

Change-Id: Ib651fa9b0fe0b220eb8cb88dde2b63d6bf54895e
diff --git a/src/test/java/edu/bjtu/groupone/backend/UserServImplTest.java b/src/test/java/edu/bjtu/groupone/backend/UserServImplTest.java
new file mode 100644
index 0000000..beacf28
--- /dev/null
+++ b/src/test/java/edu/bjtu/groupone/backend/UserServImplTest.java
@@ -0,0 +1,124 @@
+package edu.bjtu.groupone.backend;
+
+import edu.bjtu.groupone.backend.mapper.UserMapper;
+import edu.bjtu.groupone.backend.utils.EmailUtil;
+import edu.bjtu.groupone.backend.service.impl.UserServImpl;
+import edu.bjtu.groupone.backend.model.User;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.lang.reflect.Field;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * 单元测试 UserServImpl 的核心逻辑:登录、发/验注册码、发/验重置码、重置密码。
+ */
+@ExtendWith(MockitoExtension.class)
+public class UserServImplTest {
+
+    @Mock
+    private UserMapper userMapper;
+
+    @Mock
+    private EmailUtil emailUtil;
+
+    @InjectMocks
+    private UserServImpl userService;
+
+    @BeforeEach
+    void clearCaches() throws Exception {
+        clearMap("emailCodes");
+        clearMap("resetCodes");
+    }
+
+    @Test
+    public void login_shouldReturnUser_whenCredentialsMatch() {
+        User in = new User();
+        in.setEmail("a@b.com");
+        in.setPassword("pwd");
+        User out = new User();
+        out.setUserId(1);
+        out.setUsername("alice");
+        out.setEmail("a@b.com");
+        when(userMapper.login(in)).thenReturn(out);
+
+        User result = userService.login(in);
+        assertThat(result).isSameAs(out);
+        verify(userMapper, times(1)).login(in);
+    }
+
+    @Test
+    public void sendVerification_and_verifyCode_workCorrectly() throws Exception {
+        String email = "test@x.com";
+        userService.sendVerificationCode(email);
+        verify(emailUtil).sendVerificationEmail(eq(email), anyString());
+
+        String stored = readMap("emailCodes", email);
+        String code = stored.split("\\|")[0];
+        assertThat(userService.verifyCode(email, code)).isTrue();
+        assertThat(userService.verifyCode(email, "000000")).isFalse();
+
+        // 模拟过期
+        patchExpiry("emailCodes", email, -1000L);
+        assertThat(userService.verifyCode(email, code)).isFalse();
+    }
+
+    @Test
+    public void sendResetCode_and_resetPassword_workCorrectly() throws Exception {
+        String email = "reset@x.com";
+        when(userMapper.selectByEmail(email)).thenReturn(null);
+        assertThatThrownBy(() -> userService.sendResetCode(email))
+                .isInstanceOf(IllegalArgumentException.class);
+
+        User u = new User();
+        u.setEmail(email);
+        when(userMapper.selectByEmail(email)).thenReturn(u);
+
+        userService.sendResetCode(email);
+        verify(emailUtil).sendVerificationEmail(eq(email), anyString());
+        String stored = readMap("resetCodes", email);
+        String code = stored.split("\\|")[0];
+
+        when(userMapper.updatePasswordByEmail(email, "newpwd")).thenReturn(1);
+        assertThat(userService.resetPassword(email, code, "newpwd")).isTrue();
+        assertThat(userService.resetPassword(email, code, "abc")).isFalse();
+
+        userService.sendResetCode(email);
+        String stored2 = readMap("resetCodes", email);
+        String code2 = stored2.split("\\|")[0];
+        patchExpiry("resetCodes", email, -2000L);
+        assertThat(userService.resetPassword(email, code2, "pw")).isFalse();
+    }
+
+    // --- 反射辅助方法 ---
+    @SuppressWarnings("unchecked")
+    private void clearMap(String fieldName) throws Exception {
+        Field f = UserServImpl.class.getDeclaredField(fieldName);
+        f.setAccessible(true);
+        ((java.util.Map<String, String>)f.get(userService)).clear();
+    }
+
+    @SuppressWarnings("unchecked")
+    private String readMap(String fieldName, String key) throws Exception {
+        Field f = UserServImpl.class.getDeclaredField(fieldName);
+        f.setAccessible(true);
+        return ((java.util.Map<String, String>)f.get(userService)).get(key);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void patchExpiry(String fieldName, String key, long offsetMs) throws Exception {
+        String combined = readMap(fieldName, key);
+        String code = combined.split("\\|")[0];
+        long expire = System.currentTimeMillis() + offsetMs;
+        String updated = code + "|" + expire;
+        Field f = UserServImpl.class.getDeclaredField(fieldName);
+        f.setAccessible(true);
+        ((java.util.Map<String, String>)f.get(userService)).put(key, updated);
+    }
+}