重构: 店铺套餐分配系统从加价模式改为返佣模式
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m18s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m18s
主要变更: - 重构分配模型:从加价模式(pricing_mode/pricing_value)改为返佣模式(base_commission + tier_commission) - 删除独立的 my_package 接口,统一到 /api/admin/packages(通过数据权限自动过滤) - 新增批量分配和批量调价功能,支持事务和性能优化 - 新增配置版本管理,订单创建时锁定返佣配置 - 新增成本价历史记录,支持审计和纠纷处理 - 新增统计缓存系统(Redis + 异步任务),优化梯度返佣计算性能 - 删除冗余的梯度佣金独立 CRUD 接口(合并到分配配置中) - 归档 3 个已完成的 OpenSpec changes 并同步 8 个新 capabilities 到 main specs 技术细节: - 数据库迁移:000026_refactor_shop_package_allocation - 新增 Store:AllocationConfigStore, PriceHistoryStore, CommissionStatsStore - 新增 Service:BatchAllocationService, BatchPricingService, CommissionStatsService - 新增异步任务:统计更新、定时同步、周期归档 - 测试覆盖:批量操作集成测试、梯度佣金 CRUD 清理验证 影响: - API 变更:删除 4 个梯度 CRUD 接口(POST/GET/PUT/DELETE /:id/tiers) - API 新增:批量分配、批量调价接口 - 数据模型:重构 shop_series_allocation 表结构 - 性能优化:批量操作使用 CreateInBatches,统计使用 Redis 缓存 相关文档: - openspec/changes/archive/2026-01-28-refactor-shop-package-allocation/ - openspec/specs/agent-available-packages/ - openspec/specs/allocation-config-versioning/ - 等 8 个新 capability specs
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-27
|
||||
@@ -0,0 +1,169 @@
|
||||
## Context
|
||||
|
||||
### 当前状态
|
||||
|
||||
项目存在三种不同的测试基础设施方式:
|
||||
|
||||
| 方式 | 使用文件 | 问题 |
|
||||
|------|---------|------|
|
||||
| **testcontainers** | `role_test.go` | 需要 Docker,启动慢(30s+/测试),CI 环境复杂 |
|
||||
| **共享数据库 + DELETE** | `shop_management_test.go` 等 | 清理不可靠,数据残留,并行冲突 |
|
||||
| **事务隔离** | 部分单元测试 | ✅ 正确方式,已有 `testutils.NewTestTransaction` |
|
||||
|
||||
### 现有基础设施
|
||||
|
||||
`tests/testutils/db.go` 已提供:
|
||||
- `GetTestDB(t)` - 全局单例数据库连接
|
||||
- `NewTestTransaction(t)` - 创建自动回滚的测试事务
|
||||
- `GetTestRedis(t)` - 全局单例 Redis 连接
|
||||
- `CleanTestRedisKeys(t, rdb)` - 自动清理测试 Redis 键
|
||||
|
||||
问题是**集成测试没有使用这些工具**,而是各自实现了不同的方式。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 统一所有集成测试使用事务隔离模式
|
||||
- 移除 testcontainers 依赖,简化测试环境要求
|
||||
- 消除 DELETE 清理代码,改用事务自动回滚
|
||||
- 提供标准化的集成测试环境设置模式
|
||||
- 确保测试可以并行运行且互不干扰
|
||||
|
||||
**Non-Goals:**
|
||||
- 不改变测试的业务逻辑验证内容
|
||||
- 不引入新的测试框架或依赖
|
||||
- 不修改 `testutils` 的核心 API(仅增强)
|
||||
- 不处理性能测试或压力测试场景
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策 1:统一使用事务隔离模式
|
||||
|
||||
**选择**: 所有集成测试使用 `testutils.NewTestTransaction(t)` 获取独立事务
|
||||
|
||||
**理由**:
|
||||
- 已有成熟实现,无需重新开发
|
||||
- 事务回滚比 DELETE 快 100 倍以上
|
||||
- 完全隔离,支持并行执行
|
||||
- 不需要 Docker,降低环境要求
|
||||
|
||||
**放弃的替代方案**:
|
||||
- testcontainers:启动慢、需要 Docker、CI 配置复杂
|
||||
- 共享数据库 + 命名前缀:清理不可靠、并行时冲突
|
||||
|
||||
### 决策 2:增强 testutils 支持集成测试
|
||||
|
||||
**选择**: 在 `testutils` 包中添加集成测试专用的辅助函数
|
||||
|
||||
新增函数:
|
||||
```go
|
||||
// NewIntegrationTestEnv 创建集成测试环境
|
||||
// 包含:事务、Redis、Logger、TokenManager、App
|
||||
func NewIntegrationTestEnv(t *testing.T) *IntegrationTestEnv
|
||||
|
||||
// IntegrationTestEnv 集成测试环境
|
||||
type IntegrationTestEnv struct {
|
||||
TX *gorm.DB // 自动回滚的事务
|
||||
Redis *redis.Client // 全局 Redis 连接
|
||||
Logger *zap.Logger // 测试用 Logger
|
||||
TokenManager *auth.TokenManager
|
||||
App *fiber.App // 配置好的 Fiber App
|
||||
}
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 减少每个测试文件的重复代码
|
||||
- 统一 ErrorHandler、中间件配置
|
||||
- 方便后续扩展
|
||||
|
||||
### 决策 3:测试数据生成策略
|
||||
|
||||
**选择**: 使用原子计数器 + 时间戳生成唯一标识
|
||||
|
||||
```go
|
||||
// 已有实现,继续使用
|
||||
testutils.GenerateUniquePhone() // 138 + 时间戳后8位
|
||||
testutil.GenerateUniqueUsername(prefix) // prefix_counter
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 即使并行运行也不会冲突
|
||||
- 不依赖随机数(可重现)
|
||||
- 已有实现,经过验证
|
||||
|
||||
### 决策 4:Fiber App 配置统一
|
||||
|
||||
**选择**: 在 `IntegrationTestEnv` 中预配置标准的 Fiber App
|
||||
|
||||
配置内容:
|
||||
- `ErrorHandler`: 使用 `errors.SafeErrorHandler`
|
||||
- 中间件: 认证中间件(模拟用户上下文)
|
||||
- 路由: 使用 `routes.RegisterRoutes` 注册
|
||||
|
||||
**理由**:
|
||||
- 与生产环境配置一致
|
||||
- 避免每个测试文件重复配置
|
||||
- 便于测试真实的错误处理逻辑
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### 风险 1:重构范围大
|
||||
**风险**: 涉及 15-20 个测试文件,可能引入新 bug
|
||||
**缓解**:
|
||||
- 逐个文件重构,每个文件重构后立即验证
|
||||
- 保留原有测试用例逻辑,只改变环境设置方式
|
||||
- 使用 `git diff` 确保只改变了预期的部分
|
||||
|
||||
### 风险 2:事务隔离的限制
|
||||
**风险**: 某些测试可能需要真实的数据库提交(如测试并发)
|
||||
**缓解**:
|
||||
- 这类测试极少,可以特殊处理
|
||||
- 文档说明何时需要跳过事务隔离
|
||||
|
||||
### 风险 3:testcontainers 测试可能测试了特定功能
|
||||
**风险**: testcontainers 测试可能依赖完整的数据库生命周期
|
||||
**缓解**:
|
||||
- 分析每个 testcontainers 测试的实际需求
|
||||
- 大多数只需要隔离的数据库环境,事务可以满足
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### 阶段 1:增强 testutils(1-2 小时)
|
||||
1. 添加 `IntegrationTestEnv` 结构和 `NewIntegrationTestEnv` 函数
|
||||
2. 添加常用的测试辅助函数(创建测试用户、生成 Token 等)
|
||||
3. 编写使用文档
|
||||
|
||||
### 阶段 2:重构 testcontainers 测试(2-3 小时)
|
||||
1. 重构 `role_test.go`
|
||||
2. 移除 testcontainers 导入
|
||||
3. 验证所有测试通过
|
||||
|
||||
### 阶段 3:重构 DELETE 清理测试(3-4 小时)
|
||||
1. 重构 `shop_management_test.go`
|
||||
2. 重构 `shop_account_management_test.go`
|
||||
3. 重构其他使用 DELETE 清理的测试
|
||||
4. 删除所有 `teardown` 中的 DELETE 语句
|
||||
|
||||
### 阶段 4:清理和验证(1 小时)
|
||||
1. 移除 `go.mod` 中的 testcontainers 依赖
|
||||
2. 运行全量测试验证
|
||||
3. 更新测试文档
|
||||
|
||||
### 回滚策略
|
||||
- 每个阶段完成后提交
|
||||
- 如果某个阶段失败,可以 revert 到上一个阶段
|
||||
- 保留原测试文件的 git 历史,方便对比
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **是否需要保留某些 testcontainers 测试?**
|
||||
- 初步判断:不需要,所有测试都可以用事务隔离替代
|
||||
- 需要在实施时验证
|
||||
|
||||
2. **并发测试如何处理?**
|
||||
- 当前项目没有并发测试
|
||||
- 如果未来需要,可以单独处理
|
||||
|
||||
3. **测试数据库的 AutoMigrate 策略?**
|
||||
- 当前在 `GetTestDB` 首次调用时执行
|
||||
- 可能需要扩展迁移的模型列表
|
||||
@@ -0,0 +1,59 @@
|
||||
## Why
|
||||
|
||||
集成测试基础设施严重不统一,导致"单模块测试通过但全量测试失败"的问题。当前存在三种不同的测试方式:testcontainers(Docker 容器)、共享数据库 + DELETE 清理、事务隔离。这些方式混用导致测试不可靠、难以维护,且测试结果不可信。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING** 移除所有 testcontainers 依赖,统一使用事务隔离模式
|
||||
- 重构所有集成测试,使用 `testutils.NewTestTransaction` 替代直接数据库连接
|
||||
- 删除所有 `DELETE FROM ... WHERE xxx LIKE 'test%'` 的手动清理代码
|
||||
- 统一测试环境配置,从 `testutils/db.go` 集中管理,消除硬编码 DSN
|
||||
- 增强 `testutils` 包,支持集成测试的完整生命周期管理
|
||||
- 创建统一的测试环境设置模式,提供标准化的 `setupXxxTestEnv` 函数模板
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `test-infrastructure`: 统一的测试基础设施规范,包括事务隔离、Redis 清理、环境配置的标准化模式
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
<!-- 无现有 spec 需要修改 -->
|
||||
|
||||
## Impact
|
||||
|
||||
### 受影响的代码
|
||||
|
||||
| 目录/文件 | 影响 |
|
||||
|-----------|------|
|
||||
| `tests/integration/*.go` | 15-20 个测试文件需要重构 |
|
||||
| `tests/testutils/` | 增强现有工具函数 |
|
||||
| `go.mod` | 移除 testcontainers 依赖 |
|
||||
|
||||
### 具体测试文件
|
||||
|
||||
需要重构的测试文件(使用不统一方式):
|
||||
- `role_test.go` - 使用 testcontainers
|
||||
- `shop_management_test.go` - 使用 DELETE 清理
|
||||
- `shop_account_management_test.go` - 使用 DELETE 清理
|
||||
- `account_test.go` - 需要检查
|
||||
- `permission_test.go` - 需要检查
|
||||
- `carrier_test.go` - 需要检查
|
||||
- `package_test.go` - 需要检查
|
||||
- 其他集成测试文件
|
||||
|
||||
### 预期收益
|
||||
|
||||
| 指标 | 改进前 | 改进后 |
|
||||
|------|--------|--------|
|
||||
| 测试可靠性 | 不稳定,偶发失败 | 稳定,100% 可重复 |
|
||||
| 测试隔离 | 部分隔离 | 完全隔离 |
|
||||
| Docker 依赖 | 必须安装 Docker | 不需要 |
|
||||
| 测试速度 | 慢(容器启动) | 快(事务回滚) |
|
||||
| 维护成本 | 高(三种模式) | 低(一种模式) |
|
||||
|
||||
### 风险
|
||||
|
||||
- 重构范围较大,可能引入新问题
|
||||
- 需要确保所有测试在重构后仍能正确验证业务逻辑
|
||||
@@ -0,0 +1,115 @@
|
||||
# Test Infrastructure Specification
|
||||
|
||||
统一的测试基础设施规范,定义集成测试的标准化模式。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 集成测试环境结构体
|
||||
|
||||
系统 SHALL 提供 `IntegrationTestEnv` 结构体,封装集成测试所需的所有依赖。
|
||||
|
||||
结构体字段:
|
||||
- `TX *gorm.DB` - 自动回滚的数据库事务
|
||||
- `Redis *redis.Client` - 全局 Redis 连接
|
||||
- `Logger *zap.Logger` - 测试用日志记录器
|
||||
- `TokenManager *auth.TokenManager` - Token 管理器
|
||||
- `App *fiber.App` - 配置好的 Fiber 应用实例
|
||||
|
||||
#### Scenario: 创建集成测试环境
|
||||
- **WHEN** 测试调用 `testutils.NewIntegrationTestEnv(t)`
|
||||
- **THEN** 返回包含所有依赖的 `IntegrationTestEnv` 实例
|
||||
- **AND** 事务在测试结束后自动回滚
|
||||
- **AND** Redis 测试键在测试结束后自动清理
|
||||
|
||||
#### Scenario: 环境自动清理
|
||||
- **WHEN** 测试函数执行完毕(无论成功或失败)
|
||||
- **THEN** 数据库事务自动回滚
|
||||
- **AND** 测试相关的 Redis 键自动删除
|
||||
- **AND** 无需手动调用 teardown 函数
|
||||
|
||||
### Requirement: Fiber App 标准配置
|
||||
|
||||
集成测试环境中的 Fiber App MUST 使用与生产环境一致的配置。
|
||||
|
||||
配置内容:
|
||||
- ErrorHandler: 使用 `errors.SafeErrorHandler`
|
||||
- 路由注册: 使用 `routes.RegisterRoutes`
|
||||
- 认证中间件: 模拟用户上下文
|
||||
|
||||
#### Scenario: ErrorHandler 配置正确
|
||||
- **WHEN** API 返回错误
|
||||
- **THEN** 响应格式与生产环境一致(JSON 格式,包含 code、message、data)
|
||||
|
||||
#### Scenario: 路由注册完整
|
||||
- **WHEN** 创建测试环境
|
||||
- **THEN** 所有 API 路由都已注册
|
||||
- **AND** 可以测试任意 API 端点
|
||||
|
||||
### Requirement: 测试用户上下文
|
||||
|
||||
系统 SHALL 提供便捷的方式设置测试用户上下文。
|
||||
|
||||
#### Scenario: 创建超级管理员上下文
|
||||
- **WHEN** 测试需要超级管理员权限
|
||||
- **THEN** 可以通过 `env.AsSuperAdmin()` 获取带认证的请求
|
||||
- **AND** 请求自动包含有效的 Token
|
||||
|
||||
#### Scenario: 创建指定用户类型上下文
|
||||
- **WHEN** 测试需要特定用户类型(平台用户、代理、企业)
|
||||
- **THEN** 可以通过 `env.AsUser(account)` 设置用户上下文
|
||||
- **AND** 后续请求使用该用户的权限
|
||||
|
||||
### Requirement: 禁止使用 testcontainers
|
||||
|
||||
集成测试 MUST NOT 使用 testcontainers 或其他 Docker 容器方式。
|
||||
|
||||
#### Scenario: 测试不依赖 Docker
|
||||
- **WHEN** 运行集成测试
|
||||
- **THEN** 不需要 Docker 环境
|
||||
- **AND** 测试可以在任何有数据库连接的环境中运行
|
||||
|
||||
### Requirement: 禁止使用 DELETE 清理
|
||||
|
||||
集成测试 MUST NOT 使用 `DELETE FROM ... WHERE ...` 语句清理测试数据。
|
||||
|
||||
#### Scenario: 数据清理通过事务回滚
|
||||
- **WHEN** 测试创建数据
|
||||
- **THEN** 数据通过事务回滚自动清理
|
||||
- **AND** 不需要编写任何清理代码
|
||||
|
||||
### Requirement: 测试数据唯一性
|
||||
|
||||
测试生成的数据(用户名、手机号、商户代码等)MUST 保证唯一性。
|
||||
|
||||
#### Scenario: 并行测试不冲突
|
||||
- **WHEN** 多个测试并行运行
|
||||
- **THEN** 每个测试生成的数据都是唯一的
|
||||
- **AND** 不会出现 "duplicate key" 错误
|
||||
|
||||
#### Scenario: 使用唯一标识生成器
|
||||
- **WHEN** 测试需要生成手机号
|
||||
- **THEN** 使用 `testutils.GenerateUniquePhone()` 或 `testutil.GenerateUniquePhone()`
|
||||
- **AND** 生成的手机号在整个测试运行期间唯一
|
||||
|
||||
### Requirement: 测试文件统一模式
|
||||
|
||||
所有集成测试文件 MUST 遵循统一的结构模式。
|
||||
|
||||
标准模式:
|
||||
```go
|
||||
func TestXxx(t *testing.T) {
|
||||
env := testutils.NewIntegrationTestEnv(t)
|
||||
// 测试代码...
|
||||
// 无需 defer teardown
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: 标准测试结构
|
||||
- **WHEN** 编写新的集成测试
|
||||
- **THEN** 使用 `testutils.NewIntegrationTestEnv(t)` 创建环境
|
||||
- **AND** 不需要手动清理或 defer 语句
|
||||
|
||||
#### Scenario: 子测试共享环境
|
||||
- **WHEN** 测试包含多个子测试 (`t.Run`)
|
||||
- **THEN** 在父测试中创建环境
|
||||
- **AND** 所有子测试共享同一个环境
|
||||
@@ -0,0 +1,51 @@
|
||||
# 测试基础设施统一 - 任务清单
|
||||
|
||||
## 1. 增强 testutils 包
|
||||
|
||||
- [x] 1.1 创建 `IntegrationTestEnv` 结构体,封装集成测试所需的所有依赖
|
||||
- [x] 1.2 实现 `NewIntegrationTestEnv(t)` 函数,自动创建事务、Redis、Logger、TokenManager、App
|
||||
- [x] 1.3 添加 `AsSuperAdmin()` 方法,返回带超级管理员 Token 的请求
|
||||
- [x] 1.4 添加 `AsUser(account)` 方法,支持指定用户身份的请求
|
||||
- [x] 1.5 确保 `db.go` 中的 AutoMigrate 包含所有业务模型
|
||||
|
||||
## 2. 重构 testcontainers 测试(7 个文件)
|
||||
|
||||
- [x] 2.1 重构 `role_test.go` - 移除 testcontainers,使用 IntegrationTestEnv
|
||||
- [x] 2.2 重构 `permission_test.go` - 移除 testcontainers,使用 IntegrationTestEnv
|
||||
- [x] 2.3 重构 `account_test.go` - 已使用 IntegrationTestEnv,无 testcontainers
|
||||
- [x] 2.4 重构 `account_role_test.go` - 已使用 IntegrationTestEnv,无 testcontainers
|
||||
- [x] 2.5 重构 `role_permission_test.go` - 已使用 IntegrationTestEnv,无 testcontainers
|
||||
- [x] 2.6 重构 `api_regression_test.go` - 已使用 IntegrationTestEnv,无 testcontainers
|
||||
- [x] 2.7 删除 `migration_test.go` 中无意义的测试 - 项目使用独立迁移工具,保留 NoForeignKeys 检查
|
||||
|
||||
## 3. 重构 DELETE 清理测试(8 个文件)
|
||||
|
||||
- [x] 3.1 重构 `shop_management_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
- [x] 3.2 重构 `shop_account_management_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
- [x] 3.3 重构 `carrier_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
- [x] 3.4 重构 `package_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
- [x] 3.5 重构 `device_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
- [x] 3.6 重构 `iot_card_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
- [x] 3.7 重构 `authorization_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
- [x] 3.8 重构 `standalone_card_allocation_test.go` - 已使用事务隔离,无 DELETE 清理
|
||||
|
||||
## 4. 清理和验证
|
||||
|
||||
- [x] 4.1 删除无意义的测试(删除 health_test.go 的 4 个测试,migration_test.go 的 2 个跳过测试)
|
||||
- [x] 4.2 修复剩余跳过的测试
|
||||
- [x] 修复 `TestDevice_Delete` - 移除 Skip,测试正常通过
|
||||
- [x] 修复 `TestDeviceImport_TaskList` - 修正路由路径 `/import/tasks`
|
||||
- [x] 修复 `TestLoggerMiddlewareWithUserID` - 将 user_id 改为 uint 类型
|
||||
- [x] 更新 `TestIotCard_Import` 和 `TestIotCard_ImportE2E` 的 Skip 说明(E2E 测试需要 Worker 服务)
|
||||
- [x] 4.3 移除 `go.mod` 中的 testcontainers 相关依赖 - testcontainers 是 gofiber/storage 的间接依赖,无法移除
|
||||
- [x] 4.4 运行 `go mod tidy` 清理未使用的依赖
|
||||
- [x] 4.5 运行全量集成测试:**138 PASS, 3 SKIP, 0 FAIL**
|
||||
- SKIP 测试(符合预期):
|
||||
- `TestIotCard_Import` - E2E 测试需要 Worker 服务
|
||||
- `TestIotCard_ImportE2E` - E2E 测试需要 Worker 服务
|
||||
- `TestShopAccount_DeleteShopDisablesAccounts` - 功能未实现
|
||||
- [x] 4.6 更新 `docs/testing/test-connection-guide.md`,添加 IntegrationTestEnv 使用说明
|
||||
|
||||
## 5. 规范文档更新
|
||||
|
||||
- [x] 5.1 将测试规范更新到项目规范文档中(AGENTS.md)
|
||||
Reference in New Issue
Block a user