From b8c894b5cf06134f41841e35e0428d9abd09cb43 Mon Sep 17 00:00:00 2001 From: veypi Date: Tue, 17 Feb 2026 18:10:25 +0800 Subject: [PATCH] test: Refactor test infrastructure to use in-memory SQLite database - Add tests/README.md with comprehensive documentation for running and adding tests - Change TestDBFile from file-based 'test.db' to in-memory 'file::memory:?cache=shared' - Remove file cleanup in setup() and teardown() functions since memory database requires no cleanup - Simplify setup() by removing comments and streamlining database configuration --- tests/README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++ tests/main_test.go | 30 ++++++-------------- 2 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..f03e983 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,70 @@ +# VBase Integration Tests + +本目录包含 VBase 后端 API 的集成测试。这些测试验证 API 端点的功能,包括身份验证、权限检查和资源管理。 + +## 运行测试 + +在项目根目录下运行以下命令以执行所有测试: + +```bash +go test -v ./tests/... +``` + +或者运行单个测试文件: + +```bash +go test -v ./tests/auth_test.go +``` + +## 测试结构 + +测试设计为在隔离环境中运行,使用临时 SQLite 数据库和内存 Redis 模拟。 + +### 1. 全局设置 (`main_test.go`) +- **TestMain**: 测试套件的入口点。 + - 初始化 Vigo 应用程序。 + - 设置内存 SQLite 数据库 (`:memory:`)。 + - 模拟 Redis 客户端。 + - 运行所有测试。 + - 执行后无需清理资源。 + +### 2. 用户设置 (`helpers_test.go`) +- **ensureUsers**: 一个辅助函数,由需要身份验证的测试调用。 + - 检查全局 Token (`AdminToken`, `User1Token`, `User2Token`) 是否已设置。 + - 如果未设置,它会注册并登录三个标准测试用户: + - `admin_test` (管理员) + - `user1_test` (普通用户) + - `user2_test` (普通用户) + - 将它们的 ID 和 Token 存储在全局变量中,供跨测试文件使用。 + +### 3. 测试场景 +- **`none_auth_test.go`**: 检查公开端点,并验证受保护端点拒绝未认证请求。 +- **`auth_test.go`**: 测试用户生命周期事件(注册 -> 登录 -> 获取/更新个人资料 -> 登出)。 +- **`resource_perm_test.go`**: 验证用户除非获得授权(例如管理员),否则无法修改其他用户的数据。 +- **`org_permission_test.go`**: 测试组织基于角色的访问控制 (RBAC)(创建组织 -> 添加成员 -> 验证角色)。 +- **`org_load_middleware_test.go`**: 专门测试 `LoadOrg` 中间件,该中间件强制执行组织特定端点的成员资格检查。 + +## 开发注意事项 + +- **全局状态**: 我们在 `main_test.go` 中使用全局变量(如 `AdminToken`, `User1ID`)在测试函数之间共享状态。这模拟了测试运行期间的持久环境。 +- **幂等性**: `ensureUsers` 辅助函数设计为幂等的。它会优雅地处理"用户已存在"错误,允许测试在不手动清理数据库的情况下重新运行(尽管 `TestMain` 通常会重置数据库)。 +- **辅助函数**: 使用 `doRequest`, `assertStatus`, 和 `decodeResponse` (在 `helpers_test.go` 中) 保持测试代码整洁一致。 +- **数据库隔离**: 测试使用内存数据库 (`:memory:`) 以避免干扰开发数据库,并提供更快的执行速度。 + +## 添加新测试 + +1. 在此目录下创建一个新的 `_test.go` 文件。 +2. 使用 `package tests`。 +3. 如果需要已认证的用户,请在测试函数开头调用 `ensureUsers(t)`。 +4. 使用 `doRequest` 与 API 交互。 + +```go +func TestNewFeature(t *testing.T) { + ensureUsers(t) // 确保 User1Token 可用 + + t.Run("Scenario Description", func(t *testing.T) { + resp := doRequest(t, "GET", "/api/new-feature", nil, User1Token) + assertStatus(t, resp, 200) + }) +} +``` diff --git a/tests/main_test.go b/tests/main_test.go index c240f61..2650116 100644 --- a/tests/main_test.go +++ b/tests/main_test.go @@ -16,7 +16,7 @@ import ( "github.com/veypi/vigo/contrib/event" ) -const TestDBFile = "test.db" +const TestDBFile = "file::memory:?cache=shared" // Global variables to hold test data var ( @@ -42,39 +42,27 @@ func TestMain(m *testing.M) { } func setup() { - // Clean up previous run - os.Remove(TestDBFile) - // Configure for testing + // Use in-memory SQLite database cfg.Config.DB = config.Database{ Type: "sqlite", DSN: TestDBFile, } + // Use mock/memory Redis cfg.Config.Redis = config.Redis{ Addr: "memory", } + // Initialize DB connection and run migrations + if err := models.Migrate(); err != nil { + panic("failed to migrate database: " + err.Error()) + } - // Initialize DB connection - // Force re-initialization if necessary, but cfg.Config.DB.Client calls might need a reset? - // Assuming vigo/contrib/config handles this or we need to manually trigger it. - // Looking at cfg.go: var DB = Config.DB.Client - // Since DB is a variable, it might have been initialized with default values. - // We might need to rely on the fact that Config.DB.Client() creates a new connection based on current Config.DB values. - // But wait, cfg.DB is a variable initialized at package level. - // If the app uses cfg.DB directly, it might be stale. - // However, looking at main.go, it uses `models.Migrate()`. - // models/init.go probably uses cfg.DB. - - // Important: Initialize the application components - models.Migrate() + // Initialize other components event.Start() - - // Set router - // vbase.Router is already initialized in init.go } func teardown() { - os.Remove(TestDBFile) + // No cleanup needed for in-memory database } // Helpers