feat: 完成sys模块迁移,对齐PHP/Java框架
- 重构sys模块架构,严格按admin/api/core分层 - 对齐所有sys实体与数据库表结构 - 实现完整的adminapi控制器,匹配PHP/Java契约 - 修复依赖注入问题,确保服务正确注册 - 添加自动迁移工具和契约验证 - 完善多租户支持和审计功能 - 统一命名规范,与PHP业务逻辑保持一致
This commit is contained in:
116
wwjcloud/test/e2e/README.md
Normal file
116
wwjcloud/test/e2e/README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# E2E 测试脚本
|
||||
|
||||
本目录包含端到端(End-to-End)测试脚本,用于验证WWJCloud NestJS应用的完整功能。
|
||||
|
||||
## 📋 测试脚本说明
|
||||
|
||||
### 1. 模块功能测试
|
||||
|
||||
#### `modules-test.ps1` (PowerShell版本)
|
||||
- **用途**: 测试4个核心模块的API接口
|
||||
- **测试模块**: Admin、Member、RBAC、Auth
|
||||
- **运行环境**: Windows PowerShell
|
||||
- **运行方式**: `.\modules-test.ps1`
|
||||
|
||||
#### `modules-test.sh` (Bash版本)
|
||||
- **用途**: 测试4个核心模块的API接口
|
||||
- **测试模块**: Admin、Member、RBAC、Auth
|
||||
- **运行环境**: Linux/macOS Bash
|
||||
- **运行方式**: `./modules-test.sh`
|
||||
|
||||
### 2. 配置中心测试
|
||||
|
||||
#### `config-center-test.ps1`
|
||||
- **用途**: 测试配置中心功能
|
||||
- **测试功能**:
|
||||
- 系统配置获取
|
||||
- 动态配置管理
|
||||
- 配置验证
|
||||
- 配置统计
|
||||
- 动态配置创建
|
||||
- **运行环境**: Windows PowerShell
|
||||
- **运行方式**: `.\config-center-test.ps1`
|
||||
|
||||
## 🚀 使用方法
|
||||
|
||||
### 前置条件
|
||||
1. 确保WWJCloud NestJS应用正在运行(默认端口3000)
|
||||
2. 确保数据库连接正常
|
||||
3. 确保有测试用的管理员账号(默认: admin/123456)
|
||||
|
||||
### 运行测试
|
||||
|
||||
#### Windows环境
|
||||
```powershell
|
||||
# 进入测试目录
|
||||
cd test\e2e
|
||||
|
||||
# 运行模块测试
|
||||
.\modules-test.ps1
|
||||
|
||||
# 运行配置中心测试
|
||||
.\config-center-test.ps1
|
||||
```
|
||||
|
||||
#### Linux/macOS环境
|
||||
```bash
|
||||
# 进入测试目录
|
||||
cd test/e2e
|
||||
|
||||
# 给脚本执行权限
|
||||
chmod +x modules-test.sh
|
||||
|
||||
# 运行模块测试
|
||||
./modules-test.sh
|
||||
```
|
||||
|
||||
## 📊 测试覆盖范围
|
||||
|
||||
### 核心模块测试
|
||||
- ✅ **基础连接测试**: 验证应用是否正常启动
|
||||
- ✅ **Swagger文档测试**: 验证API文档是否可访问
|
||||
- ✅ **Admin模块**: 管理员CRUD操作
|
||||
- ✅ **Member模块**: 会员CRUD操作
|
||||
- ✅ **RBAC模块**: 角色权限管理
|
||||
- ✅ **Auth模块**: 认证授权功能
|
||||
- ✅ **认证接口测试**: 需要token的接口
|
||||
|
||||
### 配置中心测试
|
||||
- ✅ **登录认证**: 获取访问令牌
|
||||
- ✅ **系统配置**: 获取系统配置信息
|
||||
- ✅ **动态配置**: 配置的增删改查
|
||||
- ✅ **配置验证**: 配置有效性检查
|
||||
- ✅ **配置统计**: 配置使用统计
|
||||
|
||||
## 🔧 测试配置
|
||||
|
||||
### 默认配置
|
||||
- **应用地址**: `http://localhost:3000`
|
||||
- **管理员账号**: `admin`
|
||||
- **管理员密码**: `123456`
|
||||
|
||||
### 自定义配置
|
||||
可以修改脚本中的以下变量来适应不同环境:
|
||||
- `$BaseUrl` / `BASE_URL`: 应用访问地址
|
||||
- 登录凭据: 根据实际环境调整
|
||||
|
||||
## 📝 测试结果
|
||||
|
||||
测试脚本会输出彩色的测试结果:
|
||||
- ✅ **绿色**: 测试通过
|
||||
- ❌ **红色**: 测试失败
|
||||
- ℹ️ **蓝色**: 信息提示
|
||||
|
||||
## 🛠️ 维护说明
|
||||
|
||||
这些测试脚本应该:
|
||||
1. **定期更新**: 随着API接口的变化及时更新
|
||||
2. **持续集成**: 可集成到CI/CD流程中
|
||||
3. **环境适配**: 支持不同的部署环境
|
||||
4. **错误处理**: 提供详细的错误信息和调试帮助
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [API接口文档](../../docs/API_INTERFACE_COMPARISON.md)
|
||||
- [认证授权指南](../../docs/AUTHENTICATION_GUIDE.md)
|
||||
- [配置设置指南](../../docs/CONFIG_SETUP.md)
|
||||
102
wwjcloud/test/e2e/config-center-test.ps1
Normal file
102
wwjcloud/test/e2e/config-center-test.ps1
Normal file
@@ -0,0 +1,102 @@
|
||||
# 配置中心测试脚本
|
||||
|
||||
Write-Host "=== 配置中心功能测试 ===" -ForegroundColor Green
|
||||
|
||||
# 1. 登录获取令牌
|
||||
Write-Host "1. 登录获取令牌..." -ForegroundColor Yellow
|
||||
$loginBody = @{
|
||||
username = "admin"
|
||||
password = "123456"
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$loginResponse = Invoke-WebRequest -Uri "http://localhost:3000/adminapi/auth/login" -Method POST -ContentType "application/json" -Body $loginBody
|
||||
$loginData = $loginResponse.Content | ConvertFrom-Json
|
||||
|
||||
if ($loginData.token) {
|
||||
$token = $loginData.token
|
||||
Write-Host "✓ 登录成功,获取到令牌" -ForegroundColor Green
|
||||
|
||||
# 2. 测试系统配置接口
|
||||
Write-Host "`n2. 测试系统配置接口..." -ForegroundColor Yellow
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
|
||||
try {
|
||||
$systemConfigResponse = Invoke-WebRequest -Uri "http://localhost:3000/adminapi/config/system" -Method GET -Headers $headers
|
||||
$systemConfig = $systemConfigResponse.Content | ConvertFrom-Json
|
||||
Write-Host "✓ 系统配置获取成功" -ForegroundColor Green
|
||||
Write-Host "配置内容: $($systemConfig | ConvertTo-Json -Depth 2)" -ForegroundColor Cyan
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ 系统配置获取失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# 3. 测试动态配置列表
|
||||
Write-Host "`n3. 测试动态配置列表..." -ForegroundColor Yellow
|
||||
try {
|
||||
$dynamicConfigResponse = Invoke-WebRequest -Uri "http://localhost:3000/adminapi/config/dynamic" -Method GET -Headers $headers
|
||||
$dynamicConfig = $dynamicConfigResponse.Content | ConvertFrom-Json
|
||||
Write-Host "✓ 动态配置列表获取成功" -ForegroundColor Green
|
||||
Write-Host "动态配置: $($dynamicConfig | ConvertTo-Json)" -ForegroundColor Cyan
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ 动态配置列表获取失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# 4. 测试配置验证
|
||||
Write-Host "`n4. 测试配置验证..." -ForegroundColor Yellow
|
||||
try {
|
||||
$validateResponse = Invoke-WebRequest -Uri "http://localhost:3000/adminapi/config/validate" -Method GET -Headers $headers
|
||||
$validateResult = $validateResponse.Content | ConvertFrom-Json
|
||||
Write-Host "✓ 配置验证成功" -ForegroundColor Green
|
||||
Write-Host "验证结果: $($validateResult | ConvertTo-Json)" -ForegroundColor Cyan
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ 配置验证失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# 5. 测试配置统计
|
||||
Write-Host "`n5. 测试配置统计..." -ForegroundColor Yellow
|
||||
try {
|
||||
$statsResponse = Invoke-WebRequest -Uri "http://localhost:3000/adminapi/config/stats" -Method GET -Headers $headers
|
||||
$stats = $statsResponse.Content | ConvertFrom-Json
|
||||
Write-Host "✓ 配置统计获取成功" -ForegroundColor Green
|
||||
Write-Host "统计信息: $($stats | ConvertTo-Json)" -ForegroundColor Cyan
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ 配置统计获取失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# 6. 测试创建动态配置
|
||||
Write-Host "`n6. 测试创建动态配置..." -ForegroundColor Yellow
|
||||
$newConfigBody = @{
|
||||
key = "test.feature.flag"
|
||||
value = $true
|
||||
description = "测试功能开关"
|
||||
type = "boolean"
|
||||
category = "test"
|
||||
isPublic = $true
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$createResponse = Invoke-WebRequest -Uri "http://localhost:3000/adminapi/config/dynamic" -Method POST -Headers $headers -Body $newConfigBody
|
||||
$createResult = $createResponse.Content | ConvertFrom-Json
|
||||
Write-Host "✓ 动态配置创建成功" -ForegroundColor Green
|
||||
Write-Host "创建结果: $($createResult | ConvertTo-Json)" -ForegroundColor Cyan
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ 动态配置创建失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
} else {
|
||||
Write-Host "✗ 登录失败,未获取到令牌" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ 登录请求失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
Write-Host "`n=== 测试完成 ===" -ForegroundColor Green
|
||||
300
wwjcloud/test/e2e/modules-test.ps1
Normal file
300
wwjcloud/test/e2e/modules-test.ps1
Normal file
@@ -0,0 +1,300 @@
|
||||
# WWJ Cloud 模块测试脚本 (PowerShell版本)
|
||||
# 测试4个核心模块的API接口
|
||||
|
||||
$BaseUrl = "http://localhost:3000"
|
||||
$AdminToken = ""
|
||||
$MemberToken = ""
|
||||
|
||||
Write-Host "🚀 开始测试WWJ Cloud核心模块..." -ForegroundColor Cyan
|
||||
|
||||
# 颜色输出函数
|
||||
function Write-Success {
|
||||
param([string]$Message)
|
||||
Write-Host "✅ $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Error {
|
||||
param([string]$Message)
|
||||
Write-Host "❌ $Message" -ForegroundColor Red
|
||||
}
|
||||
|
||||
function Write-Info {
|
||||
param([string]$Message)
|
||||
Write-Host "ℹ️ $Message" -ForegroundColor Blue
|
||||
}
|
||||
|
||||
# 测试基础连接
|
||||
function Test-Connection {
|
||||
Write-Info "测试应用连接..."
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri $BaseUrl -Method GET -UseBasicParsing
|
||||
if ($response.StatusCode -eq 200) {
|
||||
Write-Success "应用连接成功"
|
||||
} else {
|
||||
Write-Error "应用连接失败: HTTP $($response.StatusCode)"
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Error "应用连接失败: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 测试Swagger文档
|
||||
function Test-Swagger {
|
||||
Write-Info "测试Swagger文档..."
|
||||
|
||||
# 测试主API文档
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/api" -Method GET -UseBasicParsing
|
||||
if ($response.StatusCode -eq 200) {
|
||||
Write-Success "主API文档可访问"
|
||||
} else {
|
||||
Write-Error "主API文档访问失败: HTTP $($response.StatusCode)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "主API文档访问失败: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 测试管理API文档
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/api/admin" -Method GET -UseBasicParsing
|
||||
if ($response.StatusCode -eq 200) {
|
||||
Write-Success "管理API文档可访问"
|
||||
} else {
|
||||
Write-Error "管理API文档访问失败: HTTP $($response.StatusCode)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "管理API文档访问失败: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# 测试Admin模块
|
||||
function Test-AdminModule {
|
||||
Write-Info "测试Admin模块..."
|
||||
|
||||
# 创建测试管理员
|
||||
$adminData = @{
|
||||
username = "testadmin"
|
||||
password = "123456"
|
||||
real_name = "测试管理员"
|
||||
status = 1
|
||||
site_id = 0
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/adminapi/admin" -Method POST -Body $adminData -ContentType "application/json" -UseBasicParsing
|
||||
if ($response.Content -match "uid") {
|
||||
Write-Success "创建管理员成功"
|
||||
} else {
|
||||
Write-Error "创建管理员失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "创建管理员失败: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 获取管理员列表
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/adminapi/admin?page=1&limit=10" -Method GET -UseBasicParsing
|
||||
if ($response.Content -match "data") {
|
||||
Write-Success "获取管理员列表成功"
|
||||
} else {
|
||||
Write-Error "获取管理员列表失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "获取管理员列表失败: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# 测试Member模块
|
||||
function Test-MemberModule {
|
||||
Write-Info "测试Member模块..."
|
||||
|
||||
# 创建测试会员
|
||||
$memberData = @{
|
||||
username = "testmember"
|
||||
password = "123456"
|
||||
nickname = "测试会员"
|
||||
mobile = "13800138000"
|
||||
email = "test@example.com"
|
||||
status = 1
|
||||
site_id = 0
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/adminapi/member" -Method POST -Body $memberData -ContentType "application/json" -UseBasicParsing
|
||||
if ($response.Content -match "member_id") {
|
||||
Write-Success "创建会员成功"
|
||||
} else {
|
||||
Write-Error "创建会员失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "创建会员失败: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 获取会员列表
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/adminapi/member?page=1&limit=10" -Method GET -UseBasicParsing
|
||||
if ($response.Content -match "data") {
|
||||
Write-Success "获取会员列表成功"
|
||||
} else {
|
||||
Write-Error "获取会员列表失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "获取会员列表失败: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# 测试RBAC模块
|
||||
function Test-RbacModule {
|
||||
Write-Info "测试RBAC模块..."
|
||||
|
||||
# 创建测试角色
|
||||
$roleData = @{
|
||||
roleName = "测试角色"
|
||||
roleDesc = "测试角色描述"
|
||||
status = 1
|
||||
appType = "admin"
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/adminapi/role" -Method POST -Body $roleData -ContentType "application/json" -UseBasicParsing
|
||||
if ($response.Content -match "roleId") {
|
||||
Write-Success "创建角色成功"
|
||||
} else {
|
||||
Write-Error "创建角色失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "创建角色失败: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 创建测试菜单
|
||||
$menuData = @{
|
||||
menuName = "测试菜单"
|
||||
menuType = 1
|
||||
status = 1
|
||||
appType = "admin"
|
||||
path = "/test"
|
||||
sort = 1
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/adminapi/menu" -Method POST -Body $menuData -ContentType "application/json" -UseBasicParsing
|
||||
if ($response.Content -match "menuId") {
|
||||
Write-Success "创建菜单成功"
|
||||
} else {
|
||||
Write-Error "创建菜单失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "创建菜单失败: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# 测试Auth模块
|
||||
function Test-AuthModule {
|
||||
Write-Info "测试Auth模块..."
|
||||
|
||||
# 测试管理员登录
|
||||
$adminLoginData = @{
|
||||
username = "admin"
|
||||
password = "123456"
|
||||
siteId = 0
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/auth/admin/login" -Method POST -Body $adminLoginData -ContentType "application/json" -UseBasicParsing
|
||||
if ($response.Content -match "accessToken") {
|
||||
Write-Success "管理员登录成功"
|
||||
# 提取token用于后续测试
|
||||
$script:AdminToken = ($response.Content | ConvertFrom-Json).accessToken
|
||||
} else {
|
||||
Write-Error "管理员登录失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "管理员登录失败: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 测试会员登录
|
||||
$memberLoginData = @{
|
||||
username = "member"
|
||||
password = "123456"
|
||||
siteId = 0
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/auth/member/login" -Method POST -Body $memberLoginData -ContentType "application/json" -UseBasicParsing
|
||||
if ($response.Content -match "accessToken") {
|
||||
Write-Success "会员登录成功"
|
||||
# 提取token用于后续测试
|
||||
$script:MemberToken = ($response.Content | ConvertFrom-Json).accessToken
|
||||
} else {
|
||||
Write-Error "会员登录失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "会员登录失败: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# 测试带认证的接口
|
||||
function Test-AuthenticatedApis {
|
||||
Write-Info "测试需要认证的接口..."
|
||||
|
||||
if ($AdminToken) {
|
||||
# 测试获取管理员统计信息
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $AdminToken"
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/adminapi/admin/stats/overview" -Method GET -Headers $headers -UseBasicParsing
|
||||
if ($response.Content -match "total") {
|
||||
Write-Success "获取管理员统计信息成功"
|
||||
} else {
|
||||
Write-Error "获取管理员统计信息失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "获取管理员统计信息失败: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($MemberToken) {
|
||||
# 测试获取会员信息
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $MemberToken"
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri "$BaseUrl/auth/profile" -Method GET -Headers $headers -UseBasicParsing
|
||||
if ($response.Content -match "userId") {
|
||||
Write-Success "获取会员信息成功"
|
||||
} else {
|
||||
Write-Error "获取会员信息失败: $($response.Content)"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "获取会员信息失败: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 主测试流程
|
||||
function Main {
|
||||
Write-Host "==========================================" -ForegroundColor Yellow
|
||||
Write-Host "WWJ Cloud 核心模块测试" -ForegroundColor Yellow
|
||||
Write-Host "==========================================" -ForegroundColor Yellow
|
||||
|
||||
Test-Connection
|
||||
Test-Swagger
|
||||
Test-AdminModule
|
||||
Test-MemberModule
|
||||
Test-RbacModule
|
||||
Test-AuthModule
|
||||
Test-AuthenticatedApis
|
||||
|
||||
Write-Host "==========================================" -ForegroundColor Yellow
|
||||
Write-Success "所有模块测试完成!"
|
||||
Write-Host "==========================================" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# 运行测试
|
||||
Main
|
||||
248
wwjcloud/test/e2e/modules-test.sh
Normal file
248
wwjcloud/test/e2e/modules-test.sh
Normal file
@@ -0,0 +1,248 @@
|
||||
#!/bin/bash
|
||||
|
||||
# WWJ Cloud 模块测试脚本
|
||||
# 测试4个核心模块的API接口
|
||||
|
||||
BASE_URL="http://localhost:3000"
|
||||
ADMIN_TOKEN=""
|
||||
MEMBER_TOKEN=""
|
||||
|
||||
echo "🚀 开始测试WWJ Cloud核心模块..."
|
||||
|
||||
# 颜色输出函数
|
||||
print_success() {
|
||||
echo -e "\033[32m✅ $1\033[0m"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "\033[31m❌ $1\033[0m"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "\033[34mℹ️ $1\033[0m"
|
||||
}
|
||||
|
||||
# 测试基础连接
|
||||
test_connection() {
|
||||
print_info "测试应用连接..."
|
||||
|
||||
response=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL")
|
||||
if [ "$response" = "200" ]; then
|
||||
print_success "应用连接成功"
|
||||
else
|
||||
print_error "应用连接失败: HTTP $response"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试Swagger文档
|
||||
test_swagger() {
|
||||
print_info "测试Swagger文档..."
|
||||
|
||||
# 测试主API文档
|
||||
response=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api")
|
||||
if [ "$response" = "200" ]; then
|
||||
print_success "主API文档可访问"
|
||||
else
|
||||
print_error "主API文档访问失败: HTTP $response"
|
||||
fi
|
||||
|
||||
# 测试管理API文档
|
||||
response=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api/admin")
|
||||
if [ "$response" = "200" ]; then
|
||||
print_success "管理API文档可访问"
|
||||
else
|
||||
print_error "管理API文档访问失败: HTTP $response"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试Admin模块
|
||||
test_admin_module() {
|
||||
print_info "测试Admin模块..."
|
||||
|
||||
# 创建测试管理员
|
||||
response=$(curl -s -X POST "$BASE_URL/adminapi/admin" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "testadmin",
|
||||
"password": "123456",
|
||||
"real_name": "测试管理员",
|
||||
"status": 1,
|
||||
"site_id": 0
|
||||
}')
|
||||
|
||||
if echo "$response" | grep -q "uid"; then
|
||||
print_success "创建管理员成功"
|
||||
else
|
||||
print_error "创建管理员失败: $response"
|
||||
fi
|
||||
|
||||
# 获取管理员列表
|
||||
response=$(curl -s "$BASE_URL/adminapi/admin?page=1&limit=10")
|
||||
if echo "$response" | grep -q "data"; then
|
||||
print_success "获取管理员列表成功"
|
||||
else
|
||||
print_error "获取管理员列表失败: $response"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试Member模块
|
||||
test_member_module() {
|
||||
print_info "测试Member模块..."
|
||||
|
||||
# 创建测试会员
|
||||
response=$(curl -s -X POST "$BASE_URL/adminapi/member" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "testmember",
|
||||
"password": "123456",
|
||||
"nickname": "测试会员",
|
||||
"mobile": "13800138000",
|
||||
"email": "test@example.com",
|
||||
"status": 1,
|
||||
"site_id": 0
|
||||
}')
|
||||
|
||||
if echo "$response" | grep -q "member_id"; then
|
||||
print_success "创建会员成功"
|
||||
else
|
||||
print_error "创建会员失败: $response"
|
||||
fi
|
||||
|
||||
# 获取会员列表
|
||||
response=$(curl -s "$BASE_URL/adminapi/member?page=1&limit=10")
|
||||
if echo "$response" | grep -q "data"; then
|
||||
print_success "获取会员列表成功"
|
||||
else
|
||||
print_error "获取会员列表失败: $response"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试RBAC模块
|
||||
test_rbac_module() {
|
||||
print_info "测试RBAC模块..."
|
||||
|
||||
# 创建测试角色
|
||||
response=$(curl -s -X POST "$BASE_URL/adminapi/role" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"roleName": "测试角色",
|
||||
"roleDesc": "测试角色描述",
|
||||
"status": 1,
|
||||
"appType": "admin"
|
||||
}')
|
||||
|
||||
if echo "$response" | grep -q "roleId"; then
|
||||
print_success "创建角色成功"
|
||||
else
|
||||
print_error "创建角色失败: $response"
|
||||
fi
|
||||
|
||||
# 创建测试菜单
|
||||
response=$(curl -s -X POST "$BASE_URL/adminapi/menu" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"menuName": "测试菜单",
|
||||
"menuType": 1,
|
||||
"status": 1,
|
||||
"appType": "admin",
|
||||
"path": "/test",
|
||||
"sort": 1
|
||||
}')
|
||||
|
||||
if echo "$response" | grep -q "menuId"; then
|
||||
print_success "创建菜单成功"
|
||||
else
|
||||
print_error "创建菜单失败: $response"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试Auth模块
|
||||
test_auth_module() {
|
||||
print_info "测试Auth模块..."
|
||||
|
||||
# 测试管理员登录
|
||||
response=$(curl -s -X POST "$BASE_URL/auth/admin/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "admin",
|
||||
"password": "123456",
|
||||
"siteId": 0
|
||||
}')
|
||||
|
||||
if echo "$response" | grep -q "accessToken"; then
|
||||
print_success "管理员登录成功"
|
||||
# 提取token用于后续测试
|
||||
ADMIN_TOKEN=$(echo "$response" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
|
||||
else
|
||||
print_error "管理员登录失败: $response"
|
||||
fi
|
||||
|
||||
# 测试会员登录
|
||||
response=$(curl -s -X POST "$BASE_URL/auth/member/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "member",
|
||||
"password": "123456",
|
||||
"siteId": 0
|
||||
}')
|
||||
|
||||
if echo "$response" | grep -q "accessToken"; then
|
||||
print_success "会员登录成功"
|
||||
# 提取token用于后续测试
|
||||
MEMBER_TOKEN=$(echo "$response" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
|
||||
else
|
||||
print_error "会员登录失败: $response"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试带认证的接口
|
||||
test_authenticated_apis() {
|
||||
print_info "测试需要认证的接口..."
|
||||
|
||||
if [ -n "$ADMIN_TOKEN" ]; then
|
||||
# 测试获取管理员统计信息
|
||||
response=$(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
"$BASE_URL/adminapi/admin/stats/overview")
|
||||
|
||||
if echo "$response" | grep -q "total"; then
|
||||
print_success "获取管理员统计信息成功"
|
||||
else
|
||||
print_error "获取管理员统计信息失败: $response"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$MEMBER_TOKEN" ]; then
|
||||
# 测试获取会员信息
|
||||
response=$(curl -s -H "Authorization: Bearer $MEMBER_TOKEN" \
|
||||
"$BASE_URL/auth/profile")
|
||||
|
||||
if echo "$response" | grep -q "userId"; then
|
||||
print_success "获取会员信息成功"
|
||||
else
|
||||
print_error "获取会员信息失败: $response"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 主测试流程
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo "WWJ Cloud 核心模块测试"
|
||||
echo "=========================================="
|
||||
|
||||
test_connection
|
||||
test_swagger
|
||||
test_admin_module
|
||||
test_member_module
|
||||
test_rbac_module
|
||||
test_auth_module
|
||||
test_authenticated_apis
|
||||
|
||||
echo "=========================================="
|
||||
print_success "所有模块测试完成!"
|
||||
echo "=========================================="
|
||||
}
|
||||
|
||||
# 运行测试
|
||||
main
|
||||
225
wwjcloud/test/e2e/security.e2e-spec.ts
Normal file
225
wwjcloud/test/e2e/security.e2e-spec.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { Controller, Get, Req, UseGuards, Module } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import request from 'supertest';
|
||||
|
||||
import { SecurityModule } from '../../src/core/security/securityModule';
|
||||
import { AdminCheckTokenGuard } from '../../src/core/security/adminCheckToken.guard';
|
||||
import { ApiCheckTokenGuard } from '../../src/core/security/apiCheckToken.guard';
|
||||
import { ApiOptionalAuthGuard } from '../../src/core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../src/core/security/siteScopeGuard';
|
||||
import { TokenAuthService } from '../../src/core/security/tokenAuth.service';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { CacheModule as NestCacheModule } from '@nestjs/cache-manager';
|
||||
|
||||
@Controller()
|
||||
class SecurityTestController {
|
||||
// 管理端受保护路由(仅校验 token)
|
||||
@Get('adminapi/secure')
|
||||
@UseGuards(AdminCheckTokenGuard)
|
||||
adminSecure(@Req() req: any) {
|
||||
return {
|
||||
appType: req.auth?.('app_type') || req.appType || '',
|
||||
uid: req.auth?.('uid') ?? req.uid ?? null,
|
||||
username: req.auth?.('username') || req.username || '',
|
||||
siteId: req.auth?.('site_id') ?? req.siteId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
// 管理端受保护路由(校验 token + 站点隔离)
|
||||
@Get('adminapi/secure-site')
|
||||
@UseGuards(AdminCheckTokenGuard, SiteScopeGuard)
|
||||
adminSecureSite(@Req() req: any) {
|
||||
return {
|
||||
appType: req.auth?.('app_type') || req.appType || '',
|
||||
uid: req.auth?.('uid') ?? req.uid ?? null,
|
||||
siteId: req.auth?.('site_id') ?? req.siteId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
// 前台受保护路由(必须登录)
|
||||
@Get('api/secure')
|
||||
@UseGuards(ApiCheckTokenGuard)
|
||||
apiSecure(@Req() req: any) {
|
||||
return {
|
||||
appType: req.auth?.('app_type') || req.appType || '',
|
||||
memberId: req.auth?.('member_id') ?? req.memberId ?? null,
|
||||
username: req.auth?.('username') || req.username || '',
|
||||
siteId: req.auth?.('site_id') ?? req.siteId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
// 前台可选登录路由(未登录也可访问)
|
||||
@Get('api/optional')
|
||||
@UseGuards(ApiOptionalAuthGuard)
|
||||
apiOptional(@Req() req: any) {
|
||||
const hasUser = !!(req.auth?.('member_id') || req.memberId);
|
||||
return {
|
||||
appType: req.auth?.('app_type') || req.appType || '',
|
||||
hasUser,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [SecurityModule, NestCacheModule.register()],
|
||||
controllers: [SecurityTestController],
|
||||
})
|
||||
class SecurityTestModule {}
|
||||
|
||||
describe('Security Guards (e2e)', () => {
|
||||
let app: any;
|
||||
let tokenService: TokenAuthService;
|
||||
|
||||
// 内存版 Redis 替身(仅实现 get/set/del)
|
||||
const memory = new Map<string, string>();
|
||||
const mockRedis = {
|
||||
async get(key: string) {
|
||||
return memory.has(key) ? memory.get(key)! : null;
|
||||
},
|
||||
async set(key: string, value: string) {
|
||||
memory.set(key, value);
|
||||
return 'OK';
|
||||
},
|
||||
async del(key: string) {
|
||||
const existed = memory.delete(key);
|
||||
return existed ? 1 : 0;
|
||||
},
|
||||
} as any;
|
||||
|
||||
// 轻量 cache-manager 替身,满足 CACHE_MANAGER 依赖
|
||||
const mockCache = {
|
||||
async get<T>(_key: string): Promise<T | null> {
|
||||
return null;
|
||||
},
|
||||
async set<T>(_key: string, _value: T, _ttl?: number): Promise<void> {
|
||||
return;
|
||||
},
|
||||
async del(_key: string): Promise<void> {
|
||||
return;
|
||||
},
|
||||
} as any;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [SecurityTestModule],
|
||||
})
|
||||
.overrideProvider('REDIS_CLIENT')
|
||||
.useValue(mockRedis)
|
||||
.overrideProvider(CACHE_MANAGER)
|
||||
.useValue(mockCache)
|
||||
.compile();
|
||||
|
||||
app = (await moduleFixture.createNestApplication()).getHttpServer
|
||||
? await moduleFixture.createNestApplication()
|
||||
: await moduleFixture.createNestApplication();
|
||||
|
||||
await app.init();
|
||||
tokenService = moduleFixture.get(TokenAuthService);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (app) {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('Admin: 401 when token missing', async () => {
|
||||
await request(app.getHttpServer()).get('/adminapi/secure').expect(401);
|
||||
});
|
||||
|
||||
it('Admin: 200 with valid token (no site header)', async () => {
|
||||
const { token } = await tokenService.createToken(
|
||||
1001,
|
||||
'admin',
|
||||
{
|
||||
uid: 1001,
|
||||
username: 'root',
|
||||
},
|
||||
600,
|
||||
);
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/adminapi/secure')
|
||||
.set('token', token)
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.data?.uid ?? res.body.uid).toBe(1001);
|
||||
const appType = res.body.data?.appType ?? res.body.appType;
|
||||
expect(appType).toBe('admin');
|
||||
});
|
||||
|
||||
it('Admin: 403 on site mismatch with SiteScopeGuard', async () => {
|
||||
const { token, params } = await tokenService.createToken(
|
||||
2002,
|
||||
'admin',
|
||||
{
|
||||
uid: 2002,
|
||||
username: 'admin2',
|
||||
site_id: 2,
|
||||
},
|
||||
600,
|
||||
);
|
||||
|
||||
// header 指定不同 site-id 触发越权
|
||||
await request(app.getHttpServer())
|
||||
.get('/adminapi/secure-site')
|
||||
.set('token', token)
|
||||
.set('site-id', '3')
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it('Admin: 200 when site matches with SiteScopeGuard', async () => {
|
||||
const { token } = await tokenService.createToken(
|
||||
2003,
|
||||
'admin',
|
||||
{
|
||||
uid: 2003,
|
||||
username: 'admin3',
|
||||
site_id: 5,
|
||||
},
|
||||
600,
|
||||
);
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/adminapi/secure-site')
|
||||
.set('token', token)
|
||||
.set('site-id', '5')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.data?.siteId ?? res.body.siteId).toBe(5);
|
||||
});
|
||||
|
||||
it('API: 401 when token missing', async () => {
|
||||
await request(app.getHttpServer()).get('/api/secure').expect(401);
|
||||
});
|
||||
|
||||
it('API: 200 with valid token', async () => {
|
||||
const { token } = await tokenService.createToken(
|
||||
3001,
|
||||
'api',
|
||||
{
|
||||
member_id: 3001,
|
||||
username: 'member1',
|
||||
site_id: 8,
|
||||
},
|
||||
600,
|
||||
);
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/secure')
|
||||
.set('token', token)
|
||||
.expect(200);
|
||||
|
||||
const memberId = res.body.data?.memberId ?? res.body.memberId;
|
||||
expect(memberId).toBe(3001);
|
||||
});
|
||||
|
||||
it('API Optional: 200 without token and no user', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/optional')
|
||||
.expect(200);
|
||||
|
||||
const hasUser = res.body.data?.hasUser ?? res.body.hasUser;
|
||||
expect(hasUser).toBe(false);
|
||||
});
|
||||
});
|
||||
75
wwjcloud/test/e2e/sys/sys.e2e-spec.ts
Normal file
75
wwjcloud/test/e2e/sys/sys.e2e-spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from '../../../src/app.module';
|
||||
|
||||
describe('SYS Module e2e', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
// 租户隔离:前台按 siteId 查询配置
|
||||
it('GET /api/config/:key should respect site isolation', async () => {
|
||||
const siteId = 0; // TODO: 准备测试数据后替换
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/config/some_key')
|
||||
.set('x-site-id', String(siteId))
|
||||
.expect(200);
|
||||
expect(res.body).toBeDefined();
|
||||
});
|
||||
|
||||
// 权限校验:无角色访问受限接口应被拒绝
|
||||
it('GET /adminapi/sys/menu/list without token should be unauthorized', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get('/adminapi/sys/menu/list')
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
// 批量读取配置
|
||||
it('GET /api/config?keys=a,b should return object', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/config')
|
||||
.query({ keys: 'a,b' })
|
||||
.expect(200);
|
||||
expect(typeof res.body).toBe('object');
|
||||
});
|
||||
|
||||
// 其他管理端受限接口 401 验证
|
||||
it('GET /adminapi/sys/dict/types without token should be unauthorized', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get('/adminapi/sys/dict/types')
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it('GET /adminapi/sys/area/list without token should be unauthorized', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get('/adminapi/sys/area/list')
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
// 前台公开接口 200 验证
|
||||
it('GET /api/dict/demo/items should return 200 (even if empty)', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/dict/demo/items')
|
||||
.expect(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
});
|
||||
|
||||
it('GET /api/area/tree should return 200 (even if empty)', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/area/tree')
|
||||
.expect(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user