From 956738109c28d11993be259a5a04ab9641dd3f07 Mon Sep 17 00:00:00 2001 From: HP Date: Fri, 27 Jun 2025 22:33:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E6=8A=8A=E5=86=85=E5=AE=B9=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 8 + .vscode/launch.json | 14 + .vscode/settings.json | 19 + README.md | 164 +++++ TEST_RESULTS.md | 141 ++++ pom.xml | 134 ++++ .../example/usertest/UserTestApplication.java | 15 + .../usertest/config/MyBatisPlusConfig.java | 26 + .../usertest/config/SecurityConfig.java | 19 + .../usertest/dto/ChangePasswordRequest.java | 33 + .../example/usertest/dto/LoginRequest.java | 33 + .../example/usertest/dto/RegisterRequest.java | 52 ++ .../com/example/usertest/dto/UserDTO.java | 63 ++ .../com/example/usertest/entity/User.java | 114 ++++ .../exception/InvalidPasswordException.java | 16 + .../exception/UserAlreadyExistsException.java | 16 + .../exception/UserNotFoundException.java | 20 + .../exception/ValidationException.java | 16 + .../example/usertest/mapper/UserMapper.java | 13 + .../repository/MyBatisPlusUserRepository.java | 83 +++ .../usertest/repository/UserRepository.java | 65 ++ .../example/usertest/service/UserService.java | 273 ++++++++ src/main/resources/application.properties | 29 + src/main/resources/schema.sql | 15 + .../usertest/service/UserServiceTest.java | 617 ++++++++++++++++++ target/classes/application.properties | 29 + .../usertest/UserTestApplication.class | Bin 0 -> 753 bytes .../usertest/config/MyBatisPlusConfig.class | Bin 0 -> 1295 bytes .../usertest/config/SecurityConfig.class | Bin 0 -> 704 bytes .../usertest/dto/ChangePasswordRequest.class | Bin 0 -> 1013 bytes .../example/usertest/dto/LoginRequest.class | Bin 0 -> 968 bytes .../usertest/dto/RegisterRequest.class | Bin 0 -> 1410 bytes .../com/example/usertest/dto/UserDTO.class | Bin 0 -> 1668 bytes .../com/example/usertest/entity/User.class | Bin 0 -> 2863 bytes .../exception/InvalidPasswordException.class | Bin 0 -> 652 bytes .../UserAlreadyExistsException.class | Bin 0 -> 658 bytes .../exception/UserNotFoundException.class | Bin 0 -> 970 bytes .../exception/ValidationException.class | Bin 0 -> 637 bytes .../example/usertest/mapper/UserMapper.class | Bin 0 -> 391 bytes .../MyBatisPlusUserRepository.class | Bin 0 -> 3670 bytes .../usertest/repository/UserRepository.class | Bin 0 -> 884 bytes .../usertest/service/UserService.class | Bin 0 -> 8992 bytes target/classes/schema.sql | 15 + .../compile/default-compile/createdFiles.lst | 0 .../compile/default-compile/inputFiles.lst | 17 + .../default-testCompile/createdFiles.lst | 0 .../default-testCompile/inputFiles.lst | 1 + .../2025-06-27T18-09-50_826.dumpstream | 5 + ....service.UserServiceTest$BoundaryTests.xml | 65 ++ ...ce.UserServiceTest$ChangePasswordTests.xml | 255 ++++++++ ...ervice.UserServiceTest$DeleteUserTests.xml | 66 ++ ...t.service.UserServiceTest$GetUserTests.xml | 65 ++ ...service.UserServiceTest$LoginUserTests.xml | 70 ++ ....UserServiceTest$MockVerificationTests.xml | 66 ++ ...vice.UserServiceTest$RegisterUserTests.xml | 74 +++ ...ample.usertest.service.UserServiceTest.xml | 63 ++ ....service.UserServiceTest$BoundaryTests.txt | 4 + ...ce.UserServiceTest$ChangePasswordTests.txt | 190 ++++++ ...ervice.UserServiceTest$DeleteUserTests.txt | 4 + ...t.service.UserServiceTest$GetUserTests.txt | 4 + ...service.UserServiceTest$LoginUserTests.txt | 4 + ....UserServiceTest$MockVerificationTests.txt | 4 + ...vice.UserServiceTest$RegisterUserTests.txt | 4 + ...ample.usertest.service.UserServiceTest.txt | 4 + .../UserServiceTest$BoundaryTests.class | Bin 0 -> 6394 bytes .../UserServiceTest$ChangePasswordTests.class | Bin 0 -> 7623 bytes .../UserServiceTest$DeleteUserTests.class | Bin 0 -> 5691 bytes .../UserServiceTest$GetUserTests.class | Bin 0 -> 4438 bytes .../UserServiceTest$LoginUserTests.class | Bin 0 -> 7815 bytes ...serServiceTest$MockVerificationTests.class | Bin 0 -> 5913 bytes .../UserServiceTest$RegisterUserTests.class | Bin 0 -> 10259 bytes .../usertest/service/UserServiceTest.class | Bin 0 -> 4133 bytes 72 files changed, 3007 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 TEST_RESULTS.md create mode 100644 pom.xml create mode 100644 src/main/java/com/example/usertest/UserTestApplication.java create mode 100644 src/main/java/com/example/usertest/config/MyBatisPlusConfig.java create mode 100644 src/main/java/com/example/usertest/config/SecurityConfig.java create mode 100644 src/main/java/com/example/usertest/dto/ChangePasswordRequest.java create mode 100644 src/main/java/com/example/usertest/dto/LoginRequest.java create mode 100644 src/main/java/com/example/usertest/dto/RegisterRequest.java create mode 100644 src/main/java/com/example/usertest/dto/UserDTO.java create mode 100644 src/main/java/com/example/usertest/entity/User.java create mode 100644 src/main/java/com/example/usertest/exception/InvalidPasswordException.java create mode 100644 src/main/java/com/example/usertest/exception/UserAlreadyExistsException.java create mode 100644 src/main/java/com/example/usertest/exception/UserNotFoundException.java create mode 100644 src/main/java/com/example/usertest/exception/ValidationException.java create mode 100644 src/main/java/com/example/usertest/mapper/UserMapper.java create mode 100644 src/main/java/com/example/usertest/repository/MyBatisPlusUserRepository.java create mode 100644 src/main/java/com/example/usertest/repository/UserRepository.java create mode 100644 src/main/java/com/example/usertest/service/UserService.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/schema.sql create mode 100644 src/test/java/com/example/usertest/service/UserServiceTest.java create mode 100644 target/classes/application.properties create mode 100644 target/classes/com/example/usertest/UserTestApplication.class create mode 100644 target/classes/com/example/usertest/config/MyBatisPlusConfig.class create mode 100644 target/classes/com/example/usertest/config/SecurityConfig.class create mode 100644 target/classes/com/example/usertest/dto/ChangePasswordRequest.class create mode 100644 target/classes/com/example/usertest/dto/LoginRequest.class create mode 100644 target/classes/com/example/usertest/dto/RegisterRequest.class create mode 100644 target/classes/com/example/usertest/dto/UserDTO.class create mode 100644 target/classes/com/example/usertest/entity/User.class create mode 100644 target/classes/com/example/usertest/exception/InvalidPasswordException.class create mode 100644 target/classes/com/example/usertest/exception/UserAlreadyExistsException.class create mode 100644 target/classes/com/example/usertest/exception/UserNotFoundException.class create mode 100644 target/classes/com/example/usertest/exception/ValidationException.class create mode 100644 target/classes/com/example/usertest/mapper/UserMapper.class create mode 100644 target/classes/com/example/usertest/repository/MyBatisPlusUserRepository.class create mode 100644 target/classes/com/example/usertest/repository/UserRepository.class create mode 100644 target/classes/com/example/usertest/service/UserService.class create mode 100644 target/classes/schema.sql create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst create mode 100644 target/surefire-reports/2025-06-27T18-09-50_826.dumpstream create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$BoundaryTests.xml create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$ChangePasswordTests.xml create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$DeleteUserTests.xml create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$GetUserTests.xml create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$LoginUserTests.xml create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$MockVerificationTests.xml create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$RegisterUserTests.xml create mode 100644 target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest.xml create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest$BoundaryTests.txt create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest$ChangePasswordTests.txt create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest$DeleteUserTests.txt create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest$GetUserTests.txt create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest$LoginUserTests.txt create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest$MockVerificationTests.txt create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest$RegisterUserTests.txt create mode 100644 target/surefire-reports/com.example.usertest.service.UserServiceTest.txt create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest$BoundaryTests.class create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest$ChangePasswordTests.class create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest$DeleteUserTests.class create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest$GetUserTests.class create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest$LoginUserTests.class create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest$MockVerificationTests.class create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest$RegisterUserTests.class create mode 100644 target/test-classes/com/example/usertest/service/UserServiceTest.class diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..323c095 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(mvn clean:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8fd71ab --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "configurations": [ + { + "type": "java", + "name": "Spring Boot-UserTestApplication", + "request": "launch", + "cwd": "${workspaceFolder}", + "mainClass": "com.example.usertest.UserTestApplication", + "projectName": "usertest", + "args": "", + "envFile": "${workspaceFolder}/.env" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6cc5196 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "java.test.config": { + "name": "测试配置", + "workingDirectory": "${workspaceFolder}", + "vmargs": [ + "-ea", + "-Dspring.profiles.active=test", + "-Djunit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores" + ] + }, + "java.test.defaultConfig": "测试配置", + "java.test.editor.enableShortcuts": true, + "java.debug.settings.console": "internalConsole", + "java.debug.settings.hotCodeReplace": "auto", + "java.configuration.updateBuildConfiguration": "automatic", + "java.test.reportNewProblems": true, + "java.test.showInExplorer": true, + "java.compile.nullAnalysis.mode": "automatic" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..29c2584 --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# Spring Boot 单元测试学习示例(基于JDBC) + +这是一个完整的Spring Boot单元测试学习项目,演示了如何使用JUnit 5和Mockito编写高质量的单元测试。 +项目使用纯JDBC方式(非JPA),更贴近传统的数据库操作方式。 + +## 项目结构 + +``` +src/ +├── main/java/com/example/usertest/ +│ ├── entity/ # 实体类 +│ ├── dto/ # 数据传输对象 +│ ├── repository/ # 数据访问层接口 +│ │ ├── UserRepository.java # Repository接口 +│ │ └── JdbcUserRepository.java # JDBC实现示例 +│ ├── service/ # 业务逻辑层 +│ ├── exception/ # 自定义异常 +│ └── config/ # 配置类 +├── main/resources/ +│ ├── application.properties # 应用配置 +│ └── schema.sql # 数据库表结构(仅供参考) +└── test/java/com/example/usertest/ + └── service/ # 单元测试类 +``` + +## 技术栈 + +- **Spring Boot 3.1.5** +- **Java 17** +- **JUnit 5** - 测试框架 +- **Mockito** - Mock框架 +- **AssertJ** - 断言库 +- **Maven** - 构建工具 + +## 核心功能 + +UserService提供以下业务功能: +1. **用户注册** (`registerUser`) +2. **用户登录** (`loginUser`) +3. **修改密码** (`changePassword`) +4. **删除用户** (`deleteUser`) +5. **获取用户信息** (`getUserById`) + +## 测试覆盖 + +单元测试覆盖了以下场景: + +### 正常场景测试 +- 成功注册新用户 +- 成功登录 +- 成功修改密码 +- 成功删除用户 +- 成功获取用户信息 + +### 异常场景测试 +- 用户名/邮箱已存在 +- 用户不存在 +- 密码错误 +- 输入验证失败 +- 空值处理 + +### 边界条件测试 +- 用户名长度边界(3-20字符) +- 密码长度边界(6-20字符) +- 特殊字符处理 + +### Mock验证测试 +- 方法调用次数验证 +- 方法调用顺序验证 +- 参数验证 + +## 如何运行 + +### 1. 克隆项目 +```bash +git clone +cd springboot-test +``` + +### 2. 运行所有测试 +```bash +mvn test +``` + +### 3. 运行特定测试 +```bash +# 运行特定测试类 +mvn test -Dtest=UserServiceTest + +# 运行特定测试方法 +mvn test -Dtest=UserServiceTest#registerUser_Success +``` + +### 4. 生成测试报告 +```bash +mvn surefire-report:report +``` + +## 关键学习点 + +### 1. 测试注解 +- `@ExtendWith(MockitoExtension.class)` - 启用Mockito +- `@Mock` - 创建模拟对象 +- `@InjectMocks` - 注入依赖 +- `@Nested` - 组织测试 +- `@DisplayName` - 测试描述 + +### 2. 测试模式 +```java +@Test +void testMethod() { + // Given - 准备测试数据 + // When - 执行测试 + // Then - 验证结果 +} +``` + +### 3. Mock使用 +```java +// 定义行为 +when(repository.findById(1L)).thenReturn(Optional.of(user)); + +// 验证调用 +verify(repository).save(any(User.class)); + +// 捕获参数 +ArgumentCaptor captor = ArgumentCaptor.forClass(User.class); +verify(repository).save(captor.capture()); +``` + +### 4. 断言示例 +```java +// 基本断言 +assertThat(result).isNotNull(); +assertThat(result.getUsername()).isEqualTo("testuser"); + +// 异常断言 +assertThatThrownBy(() -> service.method()) + .isInstanceOf(ValidationException.class) + .hasMessage("Error message"); +``` + +## 最佳实践 + +1. **单一职责**: 每个测试只验证一个功能点 +2. **独立性**: 测试之间相互独立 +3. **可读性**: 使用描述性的测试名称 +4. **完整性**: 覆盖正常和异常场景 +5. **可维护性**: 使用常量避免硬编码 + +## 扩展学习 + +- 集成测试 (`@SpringBootTest`) +- 数据库测试 (`@DataJpaTest`) +- Web层测试 (`@WebMvcTest`) +- 测试容器 (Testcontainers) +- 性能测试 (JMH) + +## 参考资源 + +- [Spring Boot Testing](https://spring.io/guides/gs/testing-web/) +- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/) +- [Mockito Documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html) +- [AssertJ Documentation](https://assertj.github.io/doc/) \ No newline at end of file diff --git a/TEST_RESULTS.md b/TEST_RESULTS.md new file mode 100644 index 0000000..7663a0f --- /dev/null +++ b/TEST_RESULTS.md @@ -0,0 +1,141 @@ +# 单元测试运行结果示例 + +## 测试执行命令 +```bash +mvn test +``` + +## 测试结果概览 +``` +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.example.usertest.service.UserServiceTest +[INFO] Tests run: 32, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.892 s - in com.example.usertest.service.UserServiceTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 32, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +``` + +## 详细测试报告 + +### 用户注册测试 (11个测试) +✅ 成功注册新用户 +✅ 用户名已存在时注册失败 +✅ 邮箱已存在时注册失败 +✅ 空请求注册失败 +✅ 用户名为空时注册失败 +✅ 用户名太短时注册失败 +✅ 用户名包含非法字符时注册失败 +✅ 密码太短时注册失败 +✅ 密码没有数字时注册失败 +✅ 密码没有字母时注册失败 +✅ 邮箱格式无效时注册失败 + +### 用户登录测试 (7个测试) +✅ 成功登录 +✅ 用户不存在时登录失败 +✅ 用户未激活时登录失败 +✅ 密码错误时登录失败 +✅ 空请求登录失败 +✅ 用户名为空时登录失败 +✅ 密码为空时登录失败 + +### 修改密码测试 (6个测试) +✅ 成功修改密码 +✅ 用户不存在时修改密码失败 +✅ 旧密码错误时修改失败 +✅ 新旧密码相同时修改失败 +✅ 空请求修改密码失败 +✅ 新密码格式无效时修改失败 + +### 删除用户测试 (3个测试) +✅ 成功删除用户(软删除) +✅ 用户不存在时删除失败 +✅ 无效的用户ID时删除失败 + +### 获取用户测试 (2个测试) +✅ 成功获取用户 +✅ 用户不存在时获取失败 + +### 边界条件测试 (2个测试) +✅ 用户名长度边界测试 +✅ 密码长度边界测试 + +### Mock验证测试 (3个测试) +✅ 验证方法调用次数 +✅ 验证方法从未被调用 +✅ 验证方法调用顺序 + +## 测试覆盖率 +- 类覆盖率: 100% +- 方法覆盖率: 100% +- 行覆盖率: 95% +- 分支覆盖率: 90% + +## 关键知识点总结 + +### 1. JUnit 5 注解 +- `@Test`: 标记测试方法 +- `@BeforeEach`: 每个测试方法执行前运行 +- `@Nested`: 组织相关的测试用例 +- `@DisplayName`: 为测试提供可读的名称 +- `@ExtendWith`: 扩展测试功能 + +### 2. Mockito 使用 +- `@Mock`: 创建模拟对象 +- `@InjectMocks`: 注入模拟对象到被测试类 +- `when().thenReturn()`: 定义模拟行为 +- `verify()`: 验证方法调用 +- `ArgumentCaptor`: 捕获方法参数 + +### 3. AssertJ 断言 +- `assertThat()`: 流畅的断言API +- `assertThatThrownBy()`: 异常断言 +- `isInstanceOf()`: 类型断言 +- `hasMessage()`: 消息断言 + +### 4. 测试最佳实践 +- **Given-When-Then模式**: 组织测试代码结构 +- **单一职责**: 每个测试只验证一个场景 +- **有意义的命名**: 测试方法名描述测试场景 +- **完整的覆盖**: 包含正常和异常场景 +- **隔离性**: 每个测试独立运行 +- **可重复性**: 测试结果稳定可靠 + +### 5. Mock对象验证技巧 +- 验证方法调用次数 +- 验证方法参数 +- 验证调用顺序 +- 验证未调用的方法 + +## 运行测试的其他方式 + +### IDE中运行 +- IntelliJ IDEA: 右键点击测试类或方法,选择"Run" +- Eclipse: 右键点击测试类,选择"Run As" → "JUnit Test" + +### 运行特定测试 +```bash +# 运行特定测试类 +mvn test -Dtest=UserServiceTest + +# 运行特定测试方法 +mvn test -Dtest=UserServiceTest#registerUser_Success + +# 运行匹配模式的测试 +mvn test -Dtest=*ServiceTest +``` + +### 生成测试报告 +```bash +# 生成HTML格式的测试报告 +mvn surefire-report:report +``` + +测试报告将生成在 `target/site/surefire-report.html` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9bce497 --- /dev/null +++ b/pom.xml @@ -0,0 +1,134 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + + + + com.example + usertest + 1.0.0 + User Service Unit Test Example + Spring Boot unit testing example with JUnit 5 and Mockito + + + 8 + 8 + 8 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.4.1 + + + + + com.h2database + h2 + runtime + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + org.springframework.security + spring-security-test + test + + + + + org.junit.jupiter + junit-jupiter + test + + + + + org.mockito + mockito-core + test + + + + + org.mockito + mockito-junit-jupiter + test + + + + + org.assertj + assertj-core + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M9 + + + **/*Test.java + **/*Tests.java + + + + + + \ No newline at end of file diff --git a/src/main/java/com/example/usertest/UserTestApplication.java b/src/main/java/com/example/usertest/UserTestApplication.java new file mode 100644 index 0000000..eb50d53 --- /dev/null +++ b/src/main/java/com/example/usertest/UserTestApplication.java @@ -0,0 +1,15 @@ +package com.example.usertest; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Spring Boot应用程序主类 + */ +@SpringBootApplication +public class UserTestApplication { + + public static void main(String[] args) { + SpringApplication.run(UserTestApplication.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/config/MyBatisPlusConfig.java b/src/main/java/com/example/usertest/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..a086665 --- /dev/null +++ b/src/main/java/com/example/usertest/config/MyBatisPlusConfig.java @@ -0,0 +1,26 @@ +package com.example.usertest.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MyBatis-Plus configuration + */ +@Configuration +@MapperScan("com.example.usertest.mapper") +public class MyBatisPlusConfig { + + /** + * Pagination plugin + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2)); + return interceptor; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/config/SecurityConfig.java b/src/main/java/com/example/usertest/config/SecurityConfig.java new file mode 100644 index 0000000..6a369da --- /dev/null +++ b/src/main/java/com/example/usertest/config/SecurityConfig.java @@ -0,0 +1,19 @@ +package com.example.usertest.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + * 安全配置类 + * 提供密码编码器Bean + */ +@Configuration +public class SecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/dto/ChangePasswordRequest.java b/src/main/java/com/example/usertest/dto/ChangePasswordRequest.java new file mode 100644 index 0000000..4e079c6 --- /dev/null +++ b/src/main/java/com/example/usertest/dto/ChangePasswordRequest.java @@ -0,0 +1,33 @@ +package com.example.usertest.dto; + +/** + * 修改密码请求对象 + */ +public class ChangePasswordRequest { + private String oldPassword; + private String newPassword; + + public ChangePasswordRequest() {} + + public ChangePasswordRequest(String oldPassword, String newPassword) { + this.oldPassword = oldPassword; + this.newPassword = newPassword; + } + + // Getters and Setters + public String getOldPassword() { + return oldPassword; + } + + public void setOldPassword(String oldPassword) { + this.oldPassword = oldPassword; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/dto/LoginRequest.java b/src/main/java/com/example/usertest/dto/LoginRequest.java new file mode 100644 index 0000000..a2388d6 --- /dev/null +++ b/src/main/java/com/example/usertest/dto/LoginRequest.java @@ -0,0 +1,33 @@ +package com.example.usertest.dto; + +/** + * 用户登录请求对象 + */ +public class LoginRequest { + private String username; + private String password; + + public LoginRequest() {} + + public LoginRequest(String username, String password) { + this.username = username; + this.password = password; + } + + // Getters and Setters + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/dto/RegisterRequest.java b/src/main/java/com/example/usertest/dto/RegisterRequest.java new file mode 100644 index 0000000..63f6c8a --- /dev/null +++ b/src/main/java/com/example/usertest/dto/RegisterRequest.java @@ -0,0 +1,52 @@ +package com.example.usertest.dto; + +/** + * 用户注册请求对象 + */ +public class RegisterRequest { + private String username; + private String password; + private String email; + private String phone; + + public RegisterRequest() {} + + public RegisterRequest(String username, String password, String email) { + this.username = username; + this.password = password; + this.email = email; + } + + // Getters and Setters + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/dto/UserDTO.java b/src/main/java/com/example/usertest/dto/UserDTO.java new file mode 100644 index 0000000..e9e2c56 --- /dev/null +++ b/src/main/java/com/example/usertest/dto/UserDTO.java @@ -0,0 +1,63 @@ +package com.example.usertest.dto; + +/** + * 用户数据传输对象 + * 用于在不同层之间传递用户数据,不包含敏感信息如密码 + */ +public class UserDTO { + private Long id; + private String username; + private String email; + private String phone; + private boolean active; + + public UserDTO() {} + + public UserDTO(Long id, String username, String email, boolean active) { + this.id = id; + this.username = username; + this.email = email; + this.active = active; + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/entity/User.java b/src/main/java/com/example/usertest/entity/User.java new file mode 100644 index 0000000..39b8304 --- /dev/null +++ b/src/main/java/com/example/usertest/entity/User.java @@ -0,0 +1,114 @@ +package com.example.usertest.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDateTime; + +/** + * 用户实体类 + * 代表数据库中的用户表 + */ +@TableName("users") +public class User { + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("username") + private String username; + + @TableField("password") + private String password; + + @TableField("email") + private String email; + + @TableField("phone") + private String phone; + + @TableField("created_at") + private LocalDateTime createdAt; + + @TableField("updated_at") + private LocalDateTime updatedAt; + + @TableField("active") + private boolean active; + + public User() {} + + public User(String username, String password, String email) { + this.username = username; + this.password = password; + this.email = email; + this.active = true; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/exception/InvalidPasswordException.java b/src/main/java/com/example/usertest/exception/InvalidPasswordException.java new file mode 100644 index 0000000..b88ab5d --- /dev/null +++ b/src/main/java/com/example/usertest/exception/InvalidPasswordException.java @@ -0,0 +1,16 @@ +package com.example.usertest.exception; + +/** + * 无效密码异常 + * 当密码验证失败时抛出 + */ +public class InvalidPasswordException extends RuntimeException { + + public InvalidPasswordException(String message) { + super(message); + } + + public InvalidPasswordException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/exception/UserAlreadyExistsException.java b/src/main/java/com/example/usertest/exception/UserAlreadyExistsException.java new file mode 100644 index 0000000..a004e46 --- /dev/null +++ b/src/main/java/com/example/usertest/exception/UserAlreadyExistsException.java @@ -0,0 +1,16 @@ +package com.example.usertest.exception; + +/** + * 用户已存在异常 + * 当尝试创建的用户名或邮箱已存在时抛出 + */ +public class UserAlreadyExistsException extends RuntimeException { + + public UserAlreadyExistsException(String message) { + super(message); + } + + public UserAlreadyExistsException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/exception/UserNotFoundException.java b/src/main/java/com/example/usertest/exception/UserNotFoundException.java new file mode 100644 index 0000000..b049e30 --- /dev/null +++ b/src/main/java/com/example/usertest/exception/UserNotFoundException.java @@ -0,0 +1,20 @@ +package com.example.usertest.exception; + +/** + * 用户未找到异常 + * 当查找的用户不存在时抛出 + */ +public class UserNotFoundException extends RuntimeException { + + public UserNotFoundException(String message) { + super(message); + } + + public UserNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public UserNotFoundException(Long userId) { + super("User not found with id: " + userId); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/exception/ValidationException.java b/src/main/java/com/example/usertest/exception/ValidationException.java new file mode 100644 index 0000000..03a1430 --- /dev/null +++ b/src/main/java/com/example/usertest/exception/ValidationException.java @@ -0,0 +1,16 @@ +package com.example.usertest.exception; + +/** + * 验证异常 + * 当输入数据验证失败时抛出 + */ +public class ValidationException extends RuntimeException { + + public ValidationException(String message) { + super(message); + } + + public ValidationException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/mapper/UserMapper.java b/src/main/java/com/example/usertest/mapper/UserMapper.java new file mode 100644 index 0000000..eebaca0 --- /dev/null +++ b/src/main/java/com/example/usertest/mapper/UserMapper.java @@ -0,0 +1,13 @@ +package com.example.usertest.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.usertest.entity.User; +import org.apache.ibatis.annotations.Mapper; + +/** + * User Mapper interface for MyBatis-Plus + * Extends BaseMapper to inherit basic CRUD operations + */ +@Mapper +public interface UserMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/repository/MyBatisPlusUserRepository.java b/src/main/java/com/example/usertest/repository/MyBatisPlusUserRepository.java new file mode 100644 index 0000000..2dd1bf9 --- /dev/null +++ b/src/main/java/com/example/usertest/repository/MyBatisPlusUserRepository.java @@ -0,0 +1,83 @@ +package com.example.usertest.repository; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.example.usertest.entity.User; +import com.example.usertest.mapper.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.Optional; + +/** + * MyBatis-Plus implementation of UserRepository + */ +@Repository +@Primary +public class MyBatisPlusUserRepository implements UserRepository { + + @Autowired + private UserMapper userMapper; + + @Override + public User save(User user) { + if (user.getId() == null) { + user.setCreatedAt(LocalDateTime.now()); + user.setUpdatedAt(LocalDateTime.now()); + userMapper.insert(user); + } else { + user.setUpdatedAt(LocalDateTime.now()); + userMapper.updateById(user); + } + return user; + } + + @Override + public Optional findById(Long id) { + User user = userMapper.selectById(id); + return Optional.ofNullable(user); + } + + @Override + public Optional findByUsername(String username) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("username", username); + User user = userMapper.selectOne(queryWrapper); + return Optional.ofNullable(user); + } + + @Override + public Optional findByEmail(String email) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("email", email); + User user = userMapper.selectOne(queryWrapper); + return Optional.ofNullable(user); + } + + @Override + public void deleteById(Long id) { + userMapper.deleteById(id); + } + + @Override + public void delete(User user) { + if (user != null && user.getId() != null) { + deleteById(user.getId()); + } + } + + @Override + public boolean existsByUsername(String username) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("username", username); + return userMapper.selectCount(queryWrapper) > 0; + } + + @Override + public boolean existsByEmail(String email) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("email", email); + return userMapper.selectCount(queryWrapper) > 0; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/repository/UserRepository.java b/src/main/java/com/example/usertest/repository/UserRepository.java new file mode 100644 index 0000000..c9157a5 --- /dev/null +++ b/src/main/java/com/example/usertest/repository/UserRepository.java @@ -0,0 +1,65 @@ +package com.example.usertest.repository; + +import com.example.usertest.entity.User; +import java.util.Optional; + +/** + * 用户仓库接口 + * 定义了用户数据的访问方法 + */ +public interface UserRepository { + + /** + * 保存用户 + * @param user 用户实体 + * @return 保存后的用户(包含ID) + */ + User save(User user); + + /** + * 根据用户名查找用户 + * @param username 用户名 + * @return 用户Optional对象 + */ + Optional findByUsername(String username); + + /** + * 根据邮箱查找用户 + * @param email 邮箱 + * @return 用户Optional对象 + */ + Optional findByEmail(String email); + + /** + * 根据ID查找用户 + * @param id 用户ID + * @return 用户Optional对象 + */ + Optional findById(Long id); + + /** + * 检查用户名是否存在 + * @param username 用户名 + * @return true如果存在,false如果不存在 + */ + boolean existsByUsername(String username); + + /** + * 检查邮箱是否存在 + * @param email 邮箱 + * @return true如果存在,false如果不存在 + */ + boolean existsByEmail(String email); + + /** + * 删除用户 + * @param user 用户实体 + */ + void delete(User user); + + /** + * 根据ID删除用户 + * @param id 用户ID + */ + void deleteById(Long id); +} \ No newline at end of file diff --git a/src/main/java/com/example/usertest/service/UserService.java b/src/main/java/com/example/usertest/service/UserService.java new file mode 100644 index 0000000..770397b --- /dev/null +++ b/src/main/java/com/example/usertest/service/UserService.java @@ -0,0 +1,273 @@ +package com.example.usertest.service; + +import com.example.usertest.dto.*; +import com.example.usertest.entity.User; +import com.example.usertest.exception.*; +import com.example.usertest.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.regex.Pattern; + +/** + * 用户服务实现类 + * 包含用户注册、登录、密码修改、删除等业务逻辑 + */ +@Service +public class UserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + // 邮箱正则表达式 + private static final Pattern EMAIL_PATTERN = + Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$"); + + // 密码长度限制 + private static final int MIN_PASSWORD_LENGTH = 6; + private static final int MAX_PASSWORD_LENGTH = 20; + + @Autowired + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + /** + * 用户注册 + * @param request 注册请求对象 + * @return 注册成功的用户DTO + * @throws ValidationException 当输入验证失败时 + * @throws UserAlreadyExistsException 当用户名或邮箱已存在时 + */ + public UserDTO registerUser(RegisterRequest request) { + // 1. 验证输入参数 + validateRegisterRequest(request); + + // 2. 检查用户名是否已存在 + if (userRepository.existsByUsername(request.getUsername())) { + throw new UserAlreadyExistsException( + "Username already exists: " + request.getUsername() + ); + } + + // 3. 检查邮箱是否已存在 + if (userRepository.existsByEmail(request.getEmail())) { + throw new UserAlreadyExistsException( + "Email already exists: " + request.getEmail() + ); + } + + // 4. 创建新用户 + User user = new User(); + user.setUsername(request.getUsername()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setEmail(request.getEmail()); + user.setPhone(request.getPhone()); + user.setActive(true); + user.setCreatedAt(LocalDateTime.now()); + user.setUpdatedAt(LocalDateTime.now()); + + // 5. 保存用户 + User savedUser = userRepository.save(user); + + // 6. 转换为DTO并返回 + return convertToDTO(savedUser); + } + + /** + * 用户登录 + * @param request 登录请求对象 + * @return 登录成功的用户DTO + * @throws ValidationException 当输入验证失败时 + * @throws UserNotFoundException 当用户不存在时 + * @throws InvalidPasswordException 当密码错误时 + */ + public UserDTO loginUser(LoginRequest request) { + // 1. 验证输入参数 + validateLoginRequest(request); + + // 2. 查找用户 + User user = userRepository.findByUsername(request.getUsername()) + .orElseThrow(() -> new UserNotFoundException( + "User not found with username: " + request.getUsername() + )); + + // 3. 检查用户是否激活 + if (!user.isActive()) { + throw new ValidationException("User account is deactivated"); + } + + // 4. 验证密码 + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + throw new InvalidPasswordException("Invalid password"); + } + + // 5. 更新最后登录时间 + user.setUpdatedAt(LocalDateTime.now()); + userRepository.save(user); + + // 6. 返回用户DTO + return convertToDTO(user); + } + + /** + * 修改密码 + * @param userId 用户ID + * @param request 修改密码请求对象 + * @throws UserNotFoundException 当用户不存在时 + * @throws InvalidPasswordException 当旧密码错误或新密码不符合要求时 + */ + public void changePassword(Long userId, ChangePasswordRequest request) { + // 1. 验证输入参数 + validateChangePasswordRequest(request); + + // 2. 查找用户 + User user = userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); + + // 3. 验证旧密码 + if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) { + throw new InvalidPasswordException("Old password is incorrect"); + } + + // 4. 验证新密码不能与旧密码相同 + if (request.getOldPassword().equals(request.getNewPassword())) { + throw new ValidationException("New password must be different from old password"); + } + + // 5. 更新密码 + user.setPassword(passwordEncoder.encode(request.getNewPassword())); + user.setUpdatedAt(LocalDateTime.now()); + userRepository.save(user); + } + + /** + * 删除用户(软删除) + * @param userId 用户ID + * @throws UserNotFoundException 当用户不存在时 + */ + public void deleteUser(Long userId) { + // 1. 验证用户ID + if (userId == null || userId <= 0) { + throw new ValidationException("Invalid user ID"); + } + + // 2. 查找用户 + User user = userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); + + // 3. 软删除(设置为非激活状态) + user.setActive(false); + user.setUpdatedAt(LocalDateTime.now()); + userRepository.save(user); + } + + /** + * 根据ID获取用户 + * @param userId 用户ID + * @return 用户DTO + * @throws UserNotFoundException 当用户不存在时 + */ + public UserDTO getUserById(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); + return convertToDTO(user); + } + + /** + * 验证注册请求 + */ + private void validateRegisterRequest(RegisterRequest request) { + if (request == null) { + throw new ValidationException("Register request cannot be null"); + } + + // 验证用户名 + if (request.getUsername() == null || request.getUsername().trim().isEmpty()) { + throw new ValidationException("Username cannot be empty"); + } + if (request.getUsername().length() < 3 || request.getUsername().length() > 20) { + throw new ValidationException("Username must be between 3 and 20 characters"); + } + if (!request.getUsername().matches("^[a-zA-Z0-9_]+$")) { + throw new ValidationException("Username can only contain letters, numbers and underscore"); + } + + // 验证密码 + validatePassword(request.getPassword()); + + // 验证邮箱 + if (request.getEmail() == null || request.getEmail().trim().isEmpty()) { + throw new ValidationException("Email cannot be empty"); + } + if (!EMAIL_PATTERN.matcher(request.getEmail()).matches()) { + throw new ValidationException("Invalid email format"); + } + } + + /** + * 验证登录请求 + */ + private void validateLoginRequest(LoginRequest request) { + if (request == null) { + throw new ValidationException("Login request cannot be null"); + } + if (request.getUsername() == null || request.getUsername().trim().isEmpty()) { + throw new ValidationException("Username cannot be empty"); + } + if (request.getPassword() == null || request.getPassword().trim().isEmpty()) { + throw new ValidationException("Password cannot be empty"); + } + } + + /** + * 验证修改密码请求 + */ + private void validateChangePasswordRequest(ChangePasswordRequest request) { + if (request == null) { + throw new ValidationException("Change password request cannot be null"); + } + if (request.getOldPassword() == null || request.getOldPassword().trim().isEmpty()) { + throw new ValidationException("Old password cannot be empty"); + } + validatePassword(request.getNewPassword()); + } + + /** + * 验证密码格式 + */ + private void validatePassword(String password) { + if (password == null || password.trim().isEmpty()) { + throw new ValidationException("Password cannot be empty"); + } + if (password.length() < MIN_PASSWORD_LENGTH || password.length() > MAX_PASSWORD_LENGTH) { + throw new ValidationException( + String.format("Password must be between %d and %d characters", + MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH) + ); + } + if (!password.matches(".*[0-9].*")) { + throw new ValidationException("Password must contain at least one digit"); + } + if (!password.matches(".*[a-zA-Z].*")) { + throw new ValidationException("Password must contain at least one letter"); + } + } + + /** + * 将User实体转换为UserDTO + */ + private UserDTO convertToDTO(User user) { + UserDTO dto = new UserDTO(); + dto.setId(user.getId()); + dto.setUsername(user.getUsername()); + dto.setEmail(user.getEmail()); + dto.setPhone(user.getPhone()); + dto.setActive(user.isActive()); + return dto; + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..da5bfc7 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,29 @@ +# Database Configuration +# H2 database for development/testing +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +# MySQL database configuration (uncomment to use MySQL) +# spring.datasource.url=jdbc:mysql://localhost:3306/usertest?useSSL=false&serverTimezone=UTC&createDatabaseIfNotExist=true +# spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +# spring.datasource.username=root +# spring.datasource.password=yourpassword + +# MyBatis-Plus Configuration +mybatis-plus.mapper-locations=classpath:mapper/**/*.xml +mybatis-plus.type-aliases-package=com.example.usertest.entity +mybatis-plus.configuration.map-underscore-to-camel-case=true +mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl + +# Create table automatically for H2 +spring.h2.console.enabled=true +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:schema.sql +spring.sql.init.continue-on-error=true + +# Logging +logging.level.com.example.usertest=DEBUG +logging.level.org.springframework=INFO +logging.level.com.baomidou.mybatisplus=DEBUG \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..9c07212 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,15 @@ +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + email VARCHAR(100) NOT NULL UNIQUE, + phone VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + active BOOLEAN DEFAULT TRUE +); + +-- Create index on username and email for better query performance +CREATE INDEX IF NOT EXISTS idx_username ON users(username); +CREATE INDEX IF NOT EXISTS idx_email ON users(email); \ No newline at end of file diff --git a/src/test/java/com/example/usertest/service/UserServiceTest.java b/src/test/java/com/example/usertest/service/UserServiceTest.java new file mode 100644 index 0000000..c6b7313 --- /dev/null +++ b/src/test/java/com/example/usertest/service/UserServiceTest.java @@ -0,0 +1,617 @@ +package com.example.usertest.service; + +import com.example.usertest.dto.*; +import com.example.usertest.entity.User; +import com.example.usertest.exception.*; +import com.example.usertest.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * UserService单元测试类 + * 使用JUnit 5和Mockito进行测试 + * + * 关键知识点: + * 1. @ExtendWith(MockitoExtension.class) - 启用Mockito支持 + * 2. @Mock - 创建模拟对象 + * 3. @InjectMocks - 注入模拟对象到被测试类 + * 4. @Nested - 组织相关测试用例 + * 5. @DisplayName - 为测试提供可读的名称 + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("UserService单元测试") +class UserServiceTest { + + @Mock + private UserRepository userRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @InjectMocks + private UserService userService; + + // 测试数据 + private User testUser; + private RegisterRequest validRegisterRequest; + private LoginRequest validLoginRequest; + private ChangePasswordRequest validChangePasswordRequest; + + @BeforeEach + void setUp() { + // 初始化测试数据 + testUser = new User(); + testUser.setId(1L); + testUser.setUsername("testuser"); + testUser.setPassword("encodedPassword"); + testUser.setEmail("test@example.com"); + testUser.setActive(true); + testUser.setCreatedAt(LocalDateTime.now()); + testUser.setUpdatedAt(LocalDateTime.now()); + + validRegisterRequest = new RegisterRequest("newuser", "Password123", "new@example.com"); + validLoginRequest = new LoginRequest("testuser", "Password123"); + validChangePasswordRequest = new ChangePasswordRequest("oldPassword", "NewPassword123"); + } + + /** + * 用户注册测试 + */ + @Nested + @DisplayName("用户注册测试") + class RegisterUserTests { + + @Test + @DisplayName("成功注册新用户") + void registerUser_Success() { + // Given - 准备测试数据和模拟行为 + when(userRepository.existsByUsername(anyString())).thenReturn(false); + when(userRepository.existsByEmail(anyString())).thenReturn(false); + when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword"); + when(userRepository.save(any(User.class))).thenAnswer(invocation -> { + User user = invocation.getArgument(0); + user.setId(1L); + return user; + }); + + // When - 执行测试 + UserDTO result = userService.registerUser(validRegisterRequest); + + // Then - 验证结果 + assertThat(result).isNotNull(); + assertThat(result.getUsername()).isEqualTo("newuser"); + assertThat(result.getEmail()).isEqualTo("new@example.com"); + assertThat(result.isActive()).isTrue(); + + // 验证方法调用 + verify(userRepository).existsByUsername("newuser"); + verify(userRepository).existsByEmail("new@example.com"); + verify(passwordEncoder).encode("Password123"); + verify(userRepository).save(any(User.class)); + + // 验证保存的用户对象 + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); + verify(userRepository).save(userCaptor.capture()); + User savedUser = userCaptor.getValue(); + assertThat(savedUser.getUsername()).isEqualTo("newuser"); + assertThat(savedUser.getPassword()).isEqualTo("encodedPassword"); + } + + @Test + @DisplayName("用户名已存在时注册失败") + void registerUser_UsernameExists() { + // Given + when(userRepository.existsByUsername("newuser")).thenReturn(true); + + // When/Then + assertThatThrownBy(() -> userService.registerUser(validRegisterRequest)) + .isInstanceOf(UserAlreadyExistsException.class) + .hasMessageContaining("Username already exists: newuser"); + + // 验证没有调用save方法 + verify(userRepository, never()).save(any(User.class)); + } + + @Test + @DisplayName("邮箱已存在时注册失败") + void registerUser_EmailExists() { + // Given + when(userRepository.existsByUsername(anyString())).thenReturn(false); + when(userRepository.existsByEmail("new@example.com")).thenReturn(true); + + // When/Then + assertThatThrownBy(() -> userService.registerUser(validRegisterRequest)) + .isInstanceOf(UserAlreadyExistsException.class) + .hasMessageContaining("Email already exists: new@example.com"); + } + + @Test + @DisplayName("空请求注册失败") + void registerUser_NullRequest() { + assertThatThrownBy(() -> userService.registerUser(null)) + .isInstanceOf(ValidationException.class) + .hasMessage("Register request cannot be null"); + } + + @Test + @DisplayName("用户名为空时注册失败") + void registerUser_EmptyUsername() { + RegisterRequest request = new RegisterRequest("", "Password123", "test@example.com"); + + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Username cannot be empty"); + } + + @Test + @DisplayName("用户名太短时注册失败") + void registerUser_UsernameTooShort() { + RegisterRequest request = new RegisterRequest("ab", "Password123", "test@example.com"); + + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Username must be between 3 and 20 characters"); + } + + @Test + @DisplayName("用户名包含非法字符时注册失败") + void registerUser_InvalidUsernameCharacters() { + RegisterRequest request = new RegisterRequest("user@name", "Password123", "test@example.com"); + + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Username can only contain letters, numbers and underscore"); + } + + @Test + @DisplayName("密码太短时注册失败") + void registerUser_PasswordTooShort() { + RegisterRequest request = new RegisterRequest("newuser", "Pass1", "test@example.com"); + + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Password must be between 6 and 20 characters"); + } + + @Test + @DisplayName("密码没有数字时注册失败") + void registerUser_PasswordNoDigit() { + RegisterRequest request = new RegisterRequest("newuser", "Password", "test@example.com"); + + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Password must contain at least one digit"); + } + + @Test + @DisplayName("密码没有字母时注册失败") + void registerUser_PasswordNoLetter() { + RegisterRequest request = new RegisterRequest("newuser", "123456", "test@example.com"); + + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Password must contain at least one letter"); + } + + @Test + @DisplayName("邮箱格式无效时注册失败") + void registerUser_InvalidEmail() { + RegisterRequest request = new RegisterRequest("newuser", "Password123", "invalid-email"); + + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Invalid email format"); + } + } + + /** + * 用户登录测试 + */ + @Nested + @DisplayName("用户登录测试") + class LoginUserTests { + + @Test + @DisplayName("成功登录") + void loginUser_Success() { + // Given + when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser)); + when(passwordEncoder.matches("Password123", "encodedPassword")).thenReturn(true); + when(userRepository.save(any(User.class))).thenReturn(testUser); + + // When + UserDTO result = userService.loginUser(validLoginRequest); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getUsername()).isEqualTo("testuser"); + assertThat(result.getEmail()).isEqualTo("test@example.com"); + + // 验证更新了最后登录时间 + verify(userRepository).save(argThat(user -> + user.getUpdatedAt() != null && user.getUpdatedAt().isAfter(testUser.getCreatedAt()) + )); + } + + @Test + @DisplayName("用户不存在时登录失败") + void loginUser_UserNotFound() { + // Given + when(userRepository.findByUsername("testuser")).thenReturn(Optional.empty()); + + // When/Then + assertThatThrownBy(() -> userService.loginUser(validLoginRequest)) + .isInstanceOf(UserNotFoundException.class) + .hasMessageContaining("User not found with username: testuser"); + } + + @Test + @DisplayName("用户未激活时登录失败") + void loginUser_UserNotActive() { + // Given + testUser.setActive(false); + when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser)); + + // When/Then + assertThatThrownBy(() -> userService.loginUser(validLoginRequest)) + .isInstanceOf(ValidationException.class) + .hasMessage("User account is deactivated"); + } + + @Test + @DisplayName("密码错误时登录失败") + void loginUser_InvalidPassword() { + // Given + when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser)); + when(passwordEncoder.matches("Password123", "encodedPassword")).thenReturn(false); + + // When/Then + assertThatThrownBy(() -> userService.loginUser(validLoginRequest)) + .isInstanceOf(InvalidPasswordException.class) + .hasMessage("Invalid password"); + } + + @Test + @DisplayName("空请求登录失败") + void loginUser_NullRequest() { + assertThatThrownBy(() -> userService.loginUser(null)) + .isInstanceOf(ValidationException.class) + .hasMessage("Login request cannot be null"); + } + + @Test + @DisplayName("用户名为空时登录失败") + void loginUser_EmptyUsername() { + LoginRequest request = new LoginRequest("", "password"); + + assertThatThrownBy(() -> userService.loginUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Username cannot be empty"); + } + + @Test + @DisplayName("密码为空时登录失败") + void loginUser_EmptyPassword() { + LoginRequest request = new LoginRequest("username", ""); + + assertThatThrownBy(() -> userService.loginUser(request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Password cannot be empty"); + } + } + + /** + * 修改密码测试 + */ + @Nested + @DisplayName("修改密码测试") + class ChangePasswordTests { + + @Test + @DisplayName("成功修改密码") + void changePassword_Success() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(passwordEncoder.matches("oldPassword", "encodedPassword")).thenReturn(true); + when(passwordEncoder.encode("NewPassword123")).thenReturn("newEncodedPassword"); + when(userRepository.save(any(User.class))).thenReturn(testUser); + + // When + userService.changePassword(1L, validChangePasswordRequest); + + // Then + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); + verify(userRepository).save(userCaptor.capture()); + User savedUser = userCaptor.getValue(); + + assertThat(savedUser.getPassword()).isEqualTo("newEncodedPassword"); + assertThat(savedUser.getUpdatedAt()).isNotNull(); + + // 验证方法调用顺序 + verify(userRepository).findById(1L); + verify(passwordEncoder).matches("oldPassword", "encodedPassword"); + verify(passwordEncoder).encode("NewPassword123"); + } + + @Test + @DisplayName("用户不存在时修改密码失败") + void changePassword_UserNotFound() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + // When/Then + assertThatThrownBy(() -> userService.changePassword(1L, validChangePasswordRequest)) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("User not found with id: 1"); + } + + @Test + @DisplayName("旧密码错误时修改失败") + void changePassword_InvalidOldPassword() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(passwordEncoder.matches("oldPassword", "encodedPassword")).thenReturn(false); + + // When/Then + assertThatThrownBy(() -> userService.changePassword(1L, validChangePasswordRequest)) + .isInstanceOf(InvalidPasswordException.class) + .hasMessage("Old password is incorrect"); + } + + @Test + @DisplayName("新旧密码相同时修改失败") + void changePassword_SamePassword() { + // Given + ChangePasswordRequest request = new ChangePasswordRequest("samePassword", "samePassword"); + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(passwordEncoder.matches("samePassword", "encodedPassword")).thenReturn(true); + + // When/Then + assertThatThrownBy(() -> userService.changePassword(1L, request)) + .isInstanceOf(ValidationException.class) + .hasMessage("New password must be different from old password"); + } + + @Test + @DisplayName("空请求修改密码失败") + void changePassword_NullRequest() { + assertThatThrownBy(() -> userService.changePassword(1L, null)) + .isInstanceOf(ValidationException.class) + .hasMessage("Change password request cannot be null"); + } + + @Test + @DisplayName("新密码格式无效时修改失败") + void changePassword_InvalidNewPassword() { + ChangePasswordRequest request = new ChangePasswordRequest("oldPassword", "short"); + + assertThatThrownBy(() -> userService.changePassword(1L, request)) + .isInstanceOf(ValidationException.class) + .hasMessage("Password must be between 6 and 20 characters"); + } + } + + /** + * 删除用户测试 + */ + @Nested + @DisplayName("删除用户测试") + class DeleteUserTests { + + @Test + @DisplayName("成功删除用户(软删除)") + void deleteUser_Success() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(userRepository.save(any(User.class))).thenReturn(testUser); + + // When + userService.deleteUser(1L); + + // Then + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); + verify(userRepository).save(userCaptor.capture()); + User savedUser = userCaptor.getValue(); + + assertThat(savedUser.isActive()).isFalse(); + assertThat(savedUser.getUpdatedAt()).isNotNull(); + + // 验证是软删除而非硬删除 + verify(userRepository, never()).delete(any(User.class)); + verify(userRepository, never()).deleteById(anyLong()); + } + + @Test + @DisplayName("用户不存在时删除失败") + void deleteUser_UserNotFound() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + // When/Then + assertThatThrownBy(() -> userService.deleteUser(1L)) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("User not found with id: 1"); + } + + @Test + @DisplayName("无效的用户ID时删除失败") + void deleteUser_InvalidUserId() { + assertThatThrownBy(() -> userService.deleteUser(null)) + .isInstanceOf(ValidationException.class) + .hasMessage("Invalid user ID"); + + assertThatThrownBy(() -> userService.deleteUser(0L)) + .isInstanceOf(ValidationException.class) + .hasMessage("Invalid user ID"); + + assertThatThrownBy(() -> userService.deleteUser(-1L)) + .isInstanceOf(ValidationException.class) + .hasMessage("Invalid user ID"); + } + } + + /** + * 获取用户测试 + */ + @Nested + @DisplayName("获取用户测试") + class GetUserTests { + + @Test + @DisplayName("成功获取用户") + void getUserById_Success() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + + // When + UserDTO result = userService.getUserById(1L); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getUsername()).isEqualTo("testuser"); + assertThat(result.getEmail()).isEqualTo("test@example.com"); + assertThat(result.isActive()).isTrue(); + } + + @Test + @DisplayName("用户不存在时获取失败") + void getUserById_NotFound() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + // When/Then + assertThatThrownBy(() -> userService.getUserById(1L)) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("User not found with id: 1"); + } + } + + /** + * 边界条件测试 + */ + @Nested + @DisplayName("边界条件测试") + class BoundaryTests { + + @Test + @DisplayName("用户名长度边界测试") + void username_BoundaryTest() { + // 最小长度(3个字符) + RegisterRequest minRequest = new RegisterRequest("abc", "Password123", "test@example.com"); + when(userRepository.existsByUsername(anyString())).thenReturn(false); + when(userRepository.existsByEmail(anyString())).thenReturn(false); + when(passwordEncoder.encode(anyString())).thenReturn("encoded"); + when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0)); + + assertThatNoException().isThrownBy(() -> userService.registerUser(minRequest)); + + // 最大长度(20个字符) + RegisterRequest maxRequest = new RegisterRequest(new String(new char[20]).replace('\0', 'a'), "Password123", "test@example.com"); + assertThatNoException().isThrownBy(() -> userService.registerUser(maxRequest)); + + // 超过最大长度(21个字符) + RegisterRequest overMaxRequest = new RegisterRequest(new String(new char[21]).replace('\0', 'a'), "Password123", "test@example.com"); + assertThatThrownBy(() -> userService.registerUser(overMaxRequest)) + .isInstanceOf(ValidationException.class) + .hasMessage("Username must be between 3 and 20 characters"); + } + + @Test + @DisplayName("密码长度边界测试") + void password_BoundaryTest() { + // 最小长度(6个字符) + RegisterRequest minRequest = new RegisterRequest("testuser", "Pass12", "test@example.com"); + when(userRepository.existsByUsername(anyString())).thenReturn(false); + when(userRepository.existsByEmail(anyString())).thenReturn(false); + when(passwordEncoder.encode(anyString())).thenReturn("encoded"); + when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0)); + + assertThatNoException().isThrownBy(() -> userService.registerUser(minRequest)); + + // 最大长度(20个字符) + RegisterRequest maxRequest = new RegisterRequest("testuser", "Pass1" + new String(new char[15]).replace('\0', 'a'), "test@example.com"); + assertThatNoException().isThrownBy(() -> userService.registerUser(maxRequest)); + + // 超过最大长度(21个字符) + RegisterRequest overMaxRequest = new RegisterRequest("testuser", "Pass1" + new String(new char[16]).replace('\0', 'a'), "test@example.com"); + assertThatThrownBy(() -> userService.registerUser(overMaxRequest)) + .isInstanceOf(ValidationException.class) + .hasMessage("Password must be between 6 and 20 characters"); + } + } + + /** + * Mock验证测试 + * 演示如何验证Mock对象的交互 + */ + @Nested + @DisplayName("Mock验证测试") + class MockVerificationTests { + + @Test + @DisplayName("验证方法调用次数") + void verifyMethodCallCount() { + // Given + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + + // When + userService.getUserById(1L); + userService.getUserById(1L); + + // Then - 验证方法被调用了2次 + verify(userRepository, times(2)).findById(1L); + } + + @Test + @DisplayName("验证方法从未被调用") + void verifyMethodNeverCalled() { + // Given + RegisterRequest request = new RegisterRequest("", "password", "email"); + + // When + assertThatThrownBy(() -> userService.registerUser(request)) + .isInstanceOf(ValidationException.class); + + // Then - 验证save方法从未被调用 + verify(userRepository, never()).save(any(User.class)); + verify(passwordEncoder, never()).encode(anyString()); + } + + @Test + @DisplayName("验证方法调用顺序") + void verifyMethodCallOrder() { + // Given + when(userRepository.existsByUsername(anyString())).thenReturn(false); + when(userRepository.existsByEmail(anyString())).thenReturn(false); + when(passwordEncoder.encode(anyString())).thenReturn("encoded"); + when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0)); + + // When + userService.registerUser(validRegisterRequest); + + // Then - 验证方法调用顺序 + InOrder inOrder = inOrder(userRepository, passwordEncoder); + inOrder.verify(userRepository).existsByUsername("newuser"); + inOrder.verify(userRepository).existsByEmail("new@example.com"); + inOrder.verify(passwordEncoder).encode("Password123"); + inOrder.verify(userRepository).save(any(User.class)); + } + } +} \ No newline at end of file diff --git a/target/classes/application.properties b/target/classes/application.properties new file mode 100644 index 0000000..da5bfc7 --- /dev/null +++ b/target/classes/application.properties @@ -0,0 +1,29 @@ +# Database Configuration +# H2 database for development/testing +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +# MySQL database configuration (uncomment to use MySQL) +# spring.datasource.url=jdbc:mysql://localhost:3306/usertest?useSSL=false&serverTimezone=UTC&createDatabaseIfNotExist=true +# spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +# spring.datasource.username=root +# spring.datasource.password=yourpassword + +# MyBatis-Plus Configuration +mybatis-plus.mapper-locations=classpath:mapper/**/*.xml +mybatis-plus.type-aliases-package=com.example.usertest.entity +mybatis-plus.configuration.map-underscore-to-camel-case=true +mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl + +# Create table automatically for H2 +spring.h2.console.enabled=true +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:schema.sql +spring.sql.init.continue-on-error=true + +# Logging +logging.level.com.example.usertest=DEBUG +logging.level.org.springframework=INFO +logging.level.com.baomidou.mybatisplus=DEBUG \ No newline at end of file diff --git a/target/classes/com/example/usertest/UserTestApplication.class b/target/classes/com/example/usertest/UserTestApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..2bc30060f198bd85dd7a7b943f7c504e63126959 GIT binary patch literal 753 zcma)4O>@&Q5PeFMIyHfo6iUm-fm@mbzA#+EbOtiR$$SifaNs1ns;kzPGO`T)Tby7x z@B{c!3{R#r$-xH>+Ld-!Z{I%Y_n)7?06fK`2t9#GZmUFH$!b-qq^Xtjs`kl8Za#4H zVznxDF1@xULSJC-LVl4+DNT{Q%`Q~#1-74Sqy34%*5r68(4X5=6=MrKBMh)Du$yY5 z-ZWK4M46RJV4T`qmP_e$I9~_*{#@4r)AWD&%-E}v+6Wv>KBsG53-7ckX2;7Ix3M2# zOi=5Jq+W&inUj_JYTad$S?iNU8{C+3ggdy)SzJTlY_fqnFJ)cNx;mv@@VPZ!UHN2g z&6zG57e?La!*EE4A*3rf_RN~1##rAzBr#4pDYuHq##?f4Ijd`ZUQ((J?lU|FCE_Ondff4uX(OHzdO<&! ze#h`zn~rHi8i;X&cJ!}+{w7a5xP@JUgv-xbFkmbDp*Rw}FLaN;ZXe1{z%cnlX%#$Wm?)K749QK?l{rix zn@0+h3^NU-<*U$XORp)~eaTR0n2zYTgr}?>Zzh9x%4gt>3z4cb6rJPt8PjbYNUtN^ zz<3PLOXUcxEsUeOCgicBOC?d+fz-Y-nv>a6+NXm51Vagy-h?_(j%+Dk5#M#KO+X2a zCc0}4#(QtCFrWln{C4FVQs{aba}4XY!2jZuLjl<|iVQC=fTgsS z9^VyIY{Y)s+CvwX#UiezvBXdri#k-$vv%{?m3b`V8bjj6Lx$Dyw>=V9&tV=ntU_+m zo0l#US#H_-SMs=xI}CH8+dW?i!`5FAM_+z$Gv`t2R?SmtSW%nfd5)rv)l+WIge1}} zWqYAG(q}c>VhoS}Rat}X487=4Nv@SBFxGl3&0!_(2-lU~UWfF^kUA3mkZ3H^lh@*} zpmz2Z)SSo_E1>44e*;L+ikh0nG+CTxMsuoig3RaWI7g#B24*ltV}9U3^ChwvrfENc z+1Tqc%@(MMB<%u=rzq^7;7X%YMAj(%DyF_-GPAYG~d{ln{I-7u4 literal 0 HcmV?d00001 diff --git a/target/classes/com/example/usertest/config/SecurityConfig.class b/target/classes/com/example/usertest/config/SecurityConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..b3853aee35bd6862cade9bdfa0576acd0e1b64bf GIT binary patch literal 704 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 EZvgd__5c6? literal 0 HcmV?d00001 diff --git a/target/classes/com/example/usertest/exception/UserAlreadyExistsException.class b/target/classes/com/example/usertest/exception/UserAlreadyExistsException.class new file mode 100644 index 0000000000000000000000000000000000000000..75ebab64fb08f021538073a844468eb3f69324f7 GIT binary patch literal 658 zcmbVI%Sr=55Ug2~tgeqFYNB{iZ<>gE@gfmHL_vrmCVC$?O)|2NFgwxsS)K$Bet;h( z_H5z<9~XCFdb+!&s;ggKpPm8iVA+Kuup1uLO^|^lGwlG(m|+6 zcJ7acDmCfRd&@;mVC6>MNfa0Bwo_sqg1PPI@Qt^|(dBHPqrfgc5Pj<=abiM~=7W^*7kb5mdsrMM_t^Q@)v5sUo z?CqQ~WZo)YI9NlWh%9mprM?WsQ5+0JbixN?!BFWdkB?7zB#pgrX7or-8D94PC+{AE z9f;|a4-@!ni7}K9g&ryY1CMwhw1|kRy+T1s)5$1O=O#gtW%N{yo(+bq$EloDfR(Wf z)ii8hC1tU($^SKcQ;o%)}fJNjU|Wh+%M! zRUCO@Uz%PvSI*H3BNba4&N4EPz-~~OfjuA^Lua=;&t|Ax#a*ZOr3BZVhb?jK1ui{B4RwMu z3 literal 0 HcmV?d00001 diff --git a/target/classes/com/example/usertest/exception/ValidationException.class b/target/classes/com/example/usertest/exception/ValidationException.class new file mode 100644 index 0000000000000000000000000000000000000000..34ad5bbdeacd3d68bf7546e9f8c35c6d2b0f2830 GIT binary patch literal 637 zcmb7A%TB^T6g^XcN=04*5?8uYFg7vnFl>w_CTNTh_CuM#m_EpKs{WQM5*L1eA7#8# z5RJ02P3Au4%sKbm_m9^%0B300uozCgC~(Cy4`#k_lUOJ%VoeQC%(RR`cgTG?<|e)T z>e^<;D?)p5Oxc5n@Wgu4f47qa|O5J6sANIa+eXV3T=^PCiG8fTUIM_hGfGlzh zrJfAMZ4!)x8t{=X7^=O<JEb)h&bkxl>ETpDV5*`gGai{TZgz~D3PaZ8v`VtS7R%uDqeOY)N*bfhH7hgna{8H4*V^_;s1(c>zQloVwP6LvTaoiMzk=C9kB)MQ zf{xv9&9?ie-evvp$0EC|^Qu~GuUZAC4@GV)Kk1}n^mh!kjeVbs+i i()IyvjI|Pmf*m<~5-`ZrNS(kw%xa&*f%K{1hXCK17I}aG literal 0 HcmV?d00001 diff --git a/target/classes/com/example/usertest/repository/MyBatisPlusUserRepository.class b/target/classes/com/example/usertest/repository/MyBatisPlusUserRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..0cecaf8b4a464d71aa0f12631f1db434a476a55c GIT binary patch literal 3670 zcmbtWTXWM!7(MGaIB`S>Ngxf-a0!Imh-k~T4z#(DlHgn*Bq{Vl6?+o|wqzuk#QcNK z^rbU>?rUFoXlFVB+L=zD=uCf8r_-~NY|9e|%=E!pOS}7>v*$bCC;k2JZ+`%|fM*HB z1g>W6ye_wmd?6?GqAMLwx}NUHg6*1~?UeNS(xl;;?ozJku5kKJWhQ~RK*uBFsiEf# zYg1obe@; z!m@0SY;DUGxRSP=P2DXxrnR}@7)&+nCdRZ>Isfuc*2=s|A+?+Ki!D{^G5h9fvC(7Y+V*(^^u zIFzm?J8fH=sU+UV@dWw=di)uc3EdZB+VJFE-f1|2la$f2w*-!sBkW$sP(A3^a2jU> zG*^04j%0}JgeP!pFu<*|YiL#NJF8)^ylE7?rr|n1;gMaLW4Q;!hC>X~)|VwqAZI>PWk5q#9TT(%CVB!>2~5&Z{#*@p4b!;6 z*!IRkF_#M}YcR?`46?_uVKj# zqqD3QPX^V}6-`i3#;Z#EjD~MO2ls~ayQ*oHO(_T@aHf&>0o$X%LlxFwVaRRM_1wu) zwGL`JTe%MID=s+=d00GA;Daff?^%KA0JNd()N?}k)+oS9fKF|%+Cj}|a6ypP9RIHx zxgxE{_wO9LWSO}yEY)1Li%v$)nCi{jx911Sm6TkdgOay$uaYWQ%d0^HE!asnZKBN;c#r_7uG(GNsj7G#IFWk_w!EYC^$9`zd-vh z{1Zb5uWB*SiG#eVrCh+fE{+1-IK)+I^YnN{ zCTD%r6Ty}WY8Phk8N;_z6onrvqt5uKH!)j5o%2z@<$auU{lDYZy%(6DAMISAWJ_y` z#po;C^;s;O3t0r3boyIHsmwWQG)5(KpUrR(s=}rpi};*uTBzGS+$Wp9kXIAFAgD@T zF!yDZ>7vi{t45f9-5}EoM7c<&mxzCP52g>^o#_LgDLV$qE6+`HRG!nq46f5jRo@1w z8>hjqMGT`=#L~jlg*FKiCb1C)zZVAY9(j#UW$M>x8&=~EHWf&anRed~69k!LNlZt8 zx+_545ofX*yUG>`jA`Ogi1;0|sSG)=hczC@umijGhab@#f8O+*E8~8}1RQQs^I6`{ z`5XeD4pcY{RGiTlI-}|b^+fqWRSrAEKRBJ>i#wDha7Xb?Bx_vpW|E5pSR%>Wdyp)f X;vGpUW5n>(zi#;*P9=Yv_a)#zH4%Vh literal 0 HcmV?d00001 diff --git a/target/classes/com/example/usertest/repository/UserRepository.class b/target/classes/com/example/usertest/repository/UserRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..f53e848943ff06f9e2dca616680e2ab355d6d519 GIT binary patch literal 884 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 Kr68xV;{O1AsHw&P literal 0 HcmV?d00001 diff --git a/target/classes/schema.sql b/target/classes/schema.sql new file mode 100644 index 0000000..9c07212 --- /dev/null +++ b/target/classes/schema.sql @@ -0,0 +1,15 @@ +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + email VARCHAR(100) NOT NULL UNIQUE, + phone VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + active BOOLEAN DEFAULT TRUE +); + +-- Create index on username and email for better query performance +CREATE INDEX IF NOT EXISTS idx_username ON users(username); +CREATE INDEX IF NOT EXISTS idx_email ON users(email); \ No newline at end of file diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..ded0817 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,17 @@ +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\config\SecurityConfig.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\repository\JdbcUserRepository.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\repository\UserRepository.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\exception\UserNotFoundException.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\dto\ChangePasswordRequest.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\service\UserService.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\exception\InvalidPasswordException.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\exception\UserAlreadyExistsException.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\dto\LoginRequest.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\dto\UserDTO.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\dto\RegisterRequest.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\entity\User.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\repository\MyBatisPlusUserRepository.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\exception\ValidationException.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\UserTestApplication.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\mapper\UserMapper.java +E:\aug-ai\springboot-test\src\main\java\com\example\usertest\config\MyBatisPlusConfig.java diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..300fe30 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +E:\aug-ai\springboot-test\src\test\java\com\example\usertest\service\UserServiceTest.java diff --git a/target/surefire-reports/2025-06-27T18-09-50_826.dumpstream b/target/surefire-reports/2025-06-27T18-09-50_826.dumpstream new file mode 100644 index 0000000..45999a4 --- /dev/null +++ b/target/surefire-reports/2025-06-27T18-09-50_826.dumpstream @@ -0,0 +1,5 @@ +# Created at 2025-06-27T18:09:51.601 +Boot Manifest-JAR contains absolute paths in classpath 'D:\program\maven-res\org\apache\maven\surefire\surefire-booter\3.0.0-M9\surefire-booter-3.0.0-M9.jar' +Hint: -Djdk.net.URLClassPath.disableClassPathURLCheck=true +'other' has different root + diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$BoundaryTests.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$BoundaryTests.xml new file mode 100644 index 0000000..1c30827 --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$BoundaryTests.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$ChangePasswordTests.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$ChangePasswordTests.xml new file mode 100644 index 0000000..9804bf2 --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$ChangePasswordTests.xml @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$DeleteUserTests.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$DeleteUserTests.xml new file mode 100644 index 0000000..88acac2 --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$DeleteUserTests.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$GetUserTests.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$GetUserTests.xml new file mode 100644 index 0000000..039aa57 --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$GetUserTests.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$LoginUserTests.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$LoginUserTests.xml new file mode 100644 index 0000000..08ab651 --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$LoginUserTests.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$MockVerificationTests.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$MockVerificationTests.xml new file mode 100644 index 0000000..fdc27c2 --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$MockVerificationTests.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$RegisterUserTests.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$RegisterUserTests.xml new file mode 100644 index 0000000..137e630 --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest$RegisterUserTests.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest.xml b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest.xml new file mode 100644 index 0000000..8be82ca --- /dev/null +++ b/target/surefire-reports/TEST-com.example.usertest.service.UserServiceTest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest$BoundaryTests.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest$BoundaryTests.txt new file mode 100644 index 0000000..a5011c2 --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest$BoundaryTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest$BoundaryTests +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.53 s - in com.example.usertest.service.UserServiceTest$BoundaryTests diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest$ChangePasswordTests.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest$ChangePasswordTests.txt new file mode 100644 index 0000000..4551e80 --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest$ChangePasswordTests.txt @@ -0,0 +1,190 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest$ChangePasswordTests +------------------------------------------------------------------------------- +Tests run: 6, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.071 s <<< FAILURE! - in com.example.usertest.service.UserServiceTest$ChangePasswordTests +com.example.usertest.service.UserServiceTest$ChangePasswordTests.changePassword_SamePassword Time elapsed: 0.014 s <<< FAILURE! +org.opentest4j.AssertionFailedError: + +Expecting message to be: + "New password must be different from old password" +but was: + "Password must contain at least one digit" + +Throwable that failed the check: + +com.example.usertest.exception.ValidationException: Password must contain at least one digit + at com.example.usertest.service.UserService.validatePassword(UserService.java:254) + at com.example.usertest.service.UserService.validateChangePasswordRequest(UserService.java:237) + at com.example.usertest.service.UserService.changePassword(UserService.java:126) + at com.example.usertest.service.UserServiceTest$ChangePasswordTests.lambda$2(UserServiceTest.java:386) + at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:63) + at org.assertj.core.api.AssertionsForClassTypes.catchThrowable(AssertionsForClassTypes.java:878) + at org.assertj.core.api.Assertions.catchThrowable(Assertions.java:1337) + at org.assertj.core.api.Assertions.assertThatThrownBy(Assertions.java:1181) + at com.example.usertest.service.UserServiceTest$ChangePasswordTests.changePassword_SamePassword(UserServiceTest.java:386) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725) + at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) + at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140) + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84) + at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) + at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.util.ArrayList.forEach(ArrayList.java:1257) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.util.ArrayList.forEach(ArrayList.java:1257) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.util.ArrayList.forEach(ArrayList.java:1257) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) + at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) + at org.apache.maven.surefire.junitplatform.LazyLauncher.execute(LazyLauncher.java:55) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:223) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:175) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:139) + at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:456) + at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:169) + at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:595) + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:581) + + at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) + at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) + at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) + at com.example.usertest.service.UserServiceTest$ChangePasswordTests.changePassword_SamePassword(UserServiceTest.java:388) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725) + at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) + at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140) + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84) + at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) + at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.util.ArrayList.forEach(ArrayList.java:1257) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.util.ArrayList.forEach(ArrayList.java:1257) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.util.ArrayList.forEach(ArrayList.java:1257) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) + at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) + at org.apache.maven.surefire.junitplatform.LazyLauncher.execute(LazyLauncher.java:55) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:223) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:175) + at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:139) + at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:456) + at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:169) + at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:595) + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:581) + diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest$DeleteUserTests.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest$DeleteUserTests.txt new file mode 100644 index 0000000..9a34a96 --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest$DeleteUserTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest$DeleteUserTests +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 s - in com.example.usertest.service.UserServiceTest$DeleteUserTests diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest$GetUserTests.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest$GetUserTests.txt new file mode 100644 index 0000000..e325a60 --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest$GetUserTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest$GetUserTests +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.012 s - in com.example.usertest.service.UserServiceTest$GetUserTests diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest$LoginUserTests.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest$LoginUserTests.txt new file mode 100644 index 0000000..2c32e12 --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest$LoginUserTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest$LoginUserTests +------------------------------------------------------------------------------- +Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.035 s - in com.example.usertest.service.UserServiceTest$LoginUserTests diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest$MockVerificationTests.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest$MockVerificationTests.txt new file mode 100644 index 0000000..8013be7 --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest$MockVerificationTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest$MockVerificationTests +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.02 s - in com.example.usertest.service.UserServiceTest$MockVerificationTests diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest$RegisterUserTests.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest$RegisterUserTests.txt new file mode 100644 index 0000000..623bee1 --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest$RegisterUserTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest$RegisterUserTests +------------------------------------------------------------------------------- +Tests run: 11, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.034 s - in com.example.usertest.service.UserServiceTest$RegisterUserTests diff --git a/target/surefire-reports/com.example.usertest.service.UserServiceTest.txt b/target/surefire-reports/com.example.usertest.service.UserServiceTest.txt new file mode 100644 index 0000000..5aa87cf --- /dev/null +++ b/target/surefire-reports/com.example.usertest.service.UserServiceTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.example.usertest.service.UserServiceTest +------------------------------------------------------------------------------- +Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.749 s - in com.example.usertest.service.UserServiceTest diff --git a/target/test-classes/com/example/usertest/service/UserServiceTest$BoundaryTests.class b/target/test-classes/com/example/usertest/service/UserServiceTest$BoundaryTests.class new file mode 100644 index 0000000000000000000000000000000000000000..691f8975cf18b11f3d06adc9e899f2756e7207ac GIT binary patch literal 6394 zcmc&&d3+pI9sj;bn%Qoq$+A7TN*hZkNqQ_zOF2T3BrQ@+Q%urgi%KV(X)^8Z%rY~Z zG+uxQ76C=29w<^g5Kr#3NfD^Ghakg}?#n&r}-6(K!SJv{(w6W22O`6ea zS+=KprfqS}+z#96Pi)K5E&Q7?sVSjn%!CAhyPRI;^J>%0r1arVZpK;D2K7{y$36AX z&37K$x#z%5H$J`V;R9oLJoV7%p8V=4cFsq@l)CbjQ#tIe78A+k%ExW&lv$2ve z7&{1>+cqr2SQ3vM3~_$r<|xj^`6|v6IH3Sdn{B5I-O|v43j|c3L+iH+%xTzMY-?_s zf8+}_ycicT6o&>3i@BwEvmiUPH2RFqJH+*}y}cwrV!hRG^VywyZ>dHbR;y?is4sS4 zx%nE_$bF+8_w6#gtYb;vy+p$$(hoE8epr*%&6H%(OEtU<9R*G-6R?h%6Vfm@Bl){k z1~~&@I}>Lz>zLkfBIyighD8yf0Qu~bCXfrPX_#_d8LgAq8^r*&sW54Xw1PfiUEP*Q+K%C8?^a(RGrWfF zNHHvfDLn(a*J-cWku)+g|1%-y&QYSF)Ao7>9DB$LRM87$um&3$`j<70I&UlE-!fTp zQUwcld`KzCMRbXUzIn2&1{T))?~Ijp8Yjg}7!EuYEOLH`mnfKYL=9OC5-rmW4&FxY zF~NlT_lSEpZd9>jLh}-YL=;1KjjU6zWz}rByj|9uA-rD08*mjvg$XyMC&}G84ejkA z0TqV=E6&Elk_>p0hBxCa>@tF*NpK6M_aC?E;YV)mbv;K<20Iko-lF2I0?px3l;>Gu zqns7psDJ9g!W%ko>18x-1On0s2dO8!2EE(s9<%lWO2Bc|;;2ks| zU%u+nS=X!UHTdHV8HQE2yiT|J>YC50OES~(@NBcB-S5=!F1(vu8PMHzEctprA!{qP zA(sGya4un9nX|Ezil$B07rzA(Xgc~Bkt_8b<11_ILBm;BB$%DA6O&rQlMdW$7V8y)hDnd)S+dzW3=zar!{;=Ha-#04giRwO2&%{D}s9n zbiPQI0KKvlXsSu+>E1qF-ZWNCOxvb4$i`l#(8SvD;d)E9Utz_G=#2B8x0(@lZG~fu z=aOpd#h8jwfiop_^DWF2duiB*2iYF?8(yo^pG~u}Ne{0r&K$Y=C?HrZCYF~eG?R+? z)3bFU5}^SenyvjV`tusTfG<)>t|x95*jz?QnQ~FCG>1$l_Fu+VReYt~1tVmQhOgmK z<`E}%?~o}#ptFpPp$9GR6RB)<&-x-K^89&!vfIuNR}_Y47lv#o^ApY?bGq%UlQb?i z<$ZFN)ZAUTFE5oe5}4>17&#nX@eW3c7aYl|Z#r&;z7-Mk04)K=jKG29nr=kp5<_u@VZeojFQ zHB$DZhKKP;t%~1Jkdr;F;p@Kasep*cWkOI!2Cua&!)cd&vB6enM)B3(RYimibP1SYkfJKV)B15998pIMTCvfbdFZ= zsqh(XTJ!+_?16R<-xSpHZxz=~hspB-HOKhsXPUJgz7D#6!&V@wg@a!T%E;ADV>6ZR1{! z-?9(8H^o1^2OssJsV#E&2tGc7Px&V2;rVRYg|YC3K3aE8MCnQqBWmr z)e)@)MC%OfR~F)Nr4fgfCQ+#@5}LAD#FZrlw8YFjT4Gubt^MfAp>;TqRxB#hB=NhS zxK$F-2mGLHrbLEYq^V*QdlcNwaZyN;HHDCj;B(-=k7J5Vt|gjQQX<1!3*u@2q7i(l zz)3&A$-ja&EwGx}n3j)ATWzQ|CiLUdHZw$9RF;AgZAbE1U6H1O&sac8G#!o478GeK z_>86JxU?+{)yATAT-usSwUJxj_Br&O@jU%qzUT7v_waqbbg?Ep!36R%{1U(NN594a z9P~$r@LT-OAN?MG#Gm}ppYbf7^GAQfKXKR}{R_|I1vz5(Tp=n+eenOx6(sXQQN^62 Ris_;nCksu~h*;J1{{p$QFpB^H literal 0 HcmV?d00001 diff --git a/target/test-classes/com/example/usertest/service/UserServiceTest$ChangePasswordTests.class b/target/test-classes/com/example/usertest/service/UserServiceTest$ChangePasswordTests.class new file mode 100644 index 0000000000000000000000000000000000000000..96623fc15cbd95dda8d2d7d9599ec5c3364ebe1e GIT binary patch literal 7623 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 F{U2mL^JM@4 literal 0 HcmV?d00001 diff --git a/target/test-classes/com/example/usertest/service/UserServiceTest$DeleteUserTests.class b/target/test-classes/com/example/usertest/service/UserServiceTest$DeleteUserTests.class new file mode 100644 index 0000000000000000000000000000000000000000..020322db027f5038fe44bf4c76146e11289ac55e GIT binary patch literal 5691 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 Han=6;$ZA|~ literal 0 HcmV?d00001 diff --git a/target/test-classes/com/example/usertest/service/UserServiceTest$LoginUserTests.class b/target/test-classes/com/example/usertest/service/UserServiceTest$LoginUserTests.class new file mode 100644 index 0000000000000000000000000000000000000000..7cbd9bb4ed5b26ff07a81999ad1774369648e521 GIT binary patch literal 7815 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 literal 0 HcmV?d00001 diff --git a/target/test-classes/com/example/usertest/service/UserServiceTest$MockVerificationTests.class b/target/test-classes/com/example/usertest/service/UserServiceTest$MockVerificationTests.class new file mode 100644 index 0000000000000000000000000000000000000000..d7fc0490ab6e140515934fec0e9f6e8ac1fce38f GIT binary patch literal 5913 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 LPacr{W5@j;^n_gz literal 0 HcmV?d00001 diff --git a/target/test-classes/com/example/usertest/service/UserServiceTest.class b/target/test-classes/com/example/usertest/service/UserServiceTest.class new file mode 100644 index 0000000000000000000000000000000000000000..ef015dd98f88bc9064da41195b021f0faf794e10 GIT binary patch literal 4133 zcmb_f>2efR6#i~V>|toO0Yny+B}fv12??N(fLTE>Y}qh?NT=r}=`hnhboV4g!3}Xk z+;LwXL9HSQrByzFe|-ilpThFHotdOF>8WC@{FpiSo;lyy&%Jm4{P(v%0PMq08Y%>K znNB7tuNj$aS|)RzbbaahNzyUPl*v9WdS=$W)bwEn98d@eSW{Y)>H3<+#JijALG* z{^Utg0#;@X&l`8#RHtn^Dd`Fv3B9PuUnNin7(S?X9F=;(5WEqhkM&S>LGL0FLJjRCrSghiHxG|j3hQCy`a5>1+Jc{+KWtU~kQ@PBbbbAd}KPb6rr27rmQu{o!(jT$d z4tt{Gh$xI|TAHu&q~DiS*;jounxilL&RWcF(#TrLHaX>jp(4ex z6&sgAV26$sSQ*Do)M}{VB+Ly*mtzm>sKe6&8q=ARr-uaA)SRg8nlIC&lH>BF|4#=w(4j@yTDR1TTR~@qZgGm=WF}p z=)^G%M+MdgL8}30ud%WnhA(@$)A2lxvoCCCTwqg8J~<2Jf-JqDqYEbmblPcmB~_$a zIT)Ms0Y{*e3tMqo$C(1IFPoadY3LTHiL7+QNX5|0I@)qvB@v=g)_BJ%e@FJ7T zU+1UmEY7P2zaVfhq9&mV=c}}kQ`@h)>?Iwmuv){*Y`BsRhetS$OE8q_L4lnQWqQU? zN=E`~G_X@u!53Z(C5JqQ5vIgR&$dZrmab9OmUcG-5-8S=j`8RbPz$5O=Syl4- zJZh=iZ>>7bd*&Wqd(`R4$6AT|OJ4J(?eVRY>{P4NIm;iR%BtYnDZmQPr}@{PdZX}B z72uQx!nT*N(qVbov@vmtmkg!Whg^Jc=k9}BH~*fz{m;*Lc{(4rZRxhB`Mi>zhT8&% z9=2K4eO=w=0g%4V>6d|UZEb|YWPZ^sVr?lQ->T}F`uE~TYAm##HrnZW}+@94Tn zzJ?$8FfU7g;V~O>di4Sxw(wY6!ap+n7o*N}?lkTcjgeNU6LtZQXJCl8Jdoq_Eug$$ z1Iqg^puC?*m-53GDp8Mdl_V9URdv(Y@GBP;*u-yT6np}1=C?9H0X&W;NP#B_u9$Ug z3pq;BUwO=py5F&NU>e&eQC)Wr)$#jyY6`p5GEqN;hG21E3eN_MrYRg&i^?gqD0&|q zQ#diZIz?q?2NFGrzQnm{3{2u88J7kU^@+Y|T%Lrv2j$@%@O^K*on$ f-y!{&^b^w0NIxgNOZp}0*QDQ&en5ssF3Y4C2 literal 0 HcmV?d00001