refactor: 一次性佣金配置从套餐级别提升到系列级别
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s
主要变更: - 新增 tb_shop_series_allocation 表,存储系列级别的一次性佣金配置 - ShopPackageAllocation 移除 one_time_commission_amount 字段 - PackageSeries 新增 enable_one_time_commission 字段控制是否启用一次性佣金 - 新增 /api/admin/shop-series-allocations CRUD 接口 - 佣金计算逻辑改为从 ShopSeriesAllocation 获取一次性佣金金额 - 删除废弃的 ShopSeriesOneTimeCommissionTier 模型 - OpenAPI Tag '系列分配' 和 '单套餐分配' 合并为 '套餐分配' 迁移脚本: - 000042: 重构佣金套餐模型 - 000043: 简化佣金分配 - 000044: 一次性佣金分配重构 - 000045: PackageSeries 添加 enable_one_time_commission 字段 测试: - 新增验收测试 (shop_series_allocation, commission_calculation) - 新增流程测试 (one_time_commission_chain) - 删除过时的单元测试(已被验收测试覆盖)
This commit is contained in:
322
tests/acceptance/README.md
Normal file
322
tests/acceptance/README.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# 验收测试 (Acceptance Tests)
|
||||
|
||||
验收测试验证单个 API 的契约:给定输入,期望输出。
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. **来源于 Spec**:每个测试用例对应 Spec 中的一个 Scenario
|
||||
2. **测试先于实现**:在功能实现前生成,预期全部 FAIL
|
||||
3. **契约验证**:验证 API 的输入输出契约,不测试内部实现
|
||||
4. **必须有破坏点**:每个测试必须注释说明什么代码变更会导致失败
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
tests/acceptance/
|
||||
├── README.md # 本文件
|
||||
├── account_acceptance_test.go # 账号管理验收测试
|
||||
├── package_acceptance_test.go # 套餐管理验收测试
|
||||
├── shop_package_acceptance_test.go # 店铺套餐分配验收测试
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 测试模板
|
||||
|
||||
```go
|
||||
package acceptance
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"junhong_cmp_fiber/tests/testutils"
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// 验收测试:{功能名称}
|
||||
// 来源:openspec/changes/{change-name}/specs/{capability}/spec.md
|
||||
// ============================================================
|
||||
|
||||
func Test{Capability}_Acceptance(t *testing.T) {
|
||||
env := testutils.NewIntegrationTestEnv(t)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: {场景名称}
|
||||
// GIVEN: {前置条件}
|
||||
// WHEN: {触发动作}
|
||||
// THEN: {预期结果}
|
||||
// AND: {额外验证}
|
||||
//
|
||||
// 破坏点:{描述什么代码变更会导致此测试失败}
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_{场景名称}", func(t *testing.T) {
|
||||
// GIVEN: 设置前置条件
|
||||
client := env.AsSuperAdmin()
|
||||
|
||||
// WHEN: 执行操作
|
||||
body := map[string]interface{}{
|
||||
// 请求体
|
||||
}
|
||||
resp, err := client.Request("POST", "/api/admin/xxx", body)
|
||||
require.NoError(t, err)
|
||||
|
||||
// THEN: 验证结果
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result map[string]interface{}
|
||||
err = resp.JSON(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, int(result["code"].(float64)))
|
||||
|
||||
// AND: 额外验证(如数据库状态)
|
||||
// ...
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 测试分类
|
||||
|
||||
### 正常场景 (Happy Path)
|
||||
|
||||
```go
|
||||
t.Run("Scenario_成功创建资源", func(t *testing.T) {
|
||||
// 测试正常流程
|
||||
})
|
||||
```
|
||||
|
||||
### 参数校验
|
||||
|
||||
```go
|
||||
t.Run("Scenario_参数缺失返回400", func(t *testing.T) {
|
||||
// 测试缺少必填参数
|
||||
})
|
||||
|
||||
t.Run("Scenario_参数格式错误返回400", func(t *testing.T) {
|
||||
// 测试参数格式不符合要求
|
||||
})
|
||||
```
|
||||
|
||||
### 权限校验
|
||||
|
||||
```go
|
||||
t.Run("Scenario_无权限返回403", func(t *testing.T) {
|
||||
// 测试权限不足的情况
|
||||
})
|
||||
|
||||
t.Run("Scenario_跨店铺访问返回403", func(t *testing.T) {
|
||||
// 测试越权访问
|
||||
})
|
||||
```
|
||||
|
||||
### 业务规则
|
||||
|
||||
```go
|
||||
t.Run("Scenario_重复创建返回409", func(t *testing.T) {
|
||||
// 测试业务规则冲突
|
||||
})
|
||||
|
||||
t.Run("Scenario_删除已使用资源返回400", func(t *testing.T) {
|
||||
// 测试业务规则限制
|
||||
})
|
||||
```
|
||||
|
||||
## 破坏点注释规范
|
||||
|
||||
每个测试必须包含"破坏点"注释,说明什么代码变更会导致测试失败:
|
||||
|
||||
```go
|
||||
// 破坏点:如果删除 handler.Create 中的 store.Create 调用,此测试将失败
|
||||
// 破坏点:如果移除参数校验中的 name 必填检查,此测试将失败
|
||||
// 破坏点:如果响应不包含创建的资源 ID,此测试将失败
|
||||
// 破坏点:如果删除权限检查中间件,此测试将失败
|
||||
```
|
||||
|
||||
**为什么需要破坏点**:
|
||||
1. 证明测试真正验证了功能
|
||||
2. 帮助理解测试意图
|
||||
3. 重构时快速定位影响
|
||||
|
||||
## Table-Driven 模式
|
||||
|
||||
对于同一 API 的多个场景,使用 table-driven 模式:
|
||||
|
||||
```go
|
||||
func TestPackage_Create_Validation(t *testing.T) {
|
||||
env := testutils.NewIntegrationTestEnv(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
body map[string]interface{}
|
||||
expectedStatus int
|
||||
expectedCode int
|
||||
breakpoint string
|
||||
}{
|
||||
{
|
||||
name: "名称为空",
|
||||
body: map[string]interface{}{
|
||||
"name": "",
|
||||
"price": 9900,
|
||||
},
|
||||
expectedStatus: 400,
|
||||
expectedCode: 4000, // CodeInvalidParam
|
||||
breakpoint: "移除 name 必填校验",
|
||||
},
|
||||
{
|
||||
name: "价格为负",
|
||||
body: map[string]interface{}{
|
||||
"name": "测试套餐",
|
||||
"price": -100,
|
||||
},
|
||||
expectedStatus: 400,
|
||||
expectedCode: 4000,
|
||||
breakpoint: "移除 price >= 0 校验",
|
||||
},
|
||||
{
|
||||
name: "时长为0",
|
||||
body: map[string]interface{}{
|
||||
"name": "测试套餐",
|
||||
"price": 9900,
|
||||
"duration": 0,
|
||||
},
|
||||
expectedStatus: 400,
|
||||
expectedCode: 4000,
|
||||
breakpoint: "移除 duration > 0 校验",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("Scenario_"+tt.name, func(t *testing.T) {
|
||||
// 破坏点: {tt.breakpoint}
|
||||
client := env.AsSuperAdmin()
|
||||
|
||||
resp, err := client.Request("POST", "/api/admin/packages", tt.body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.expectedStatus, resp.StatusCode)
|
||||
|
||||
var result map[string]interface{}
|
||||
err = resp.JSON(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedCode, int(result["code"].(float64)))
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有验收测试
|
||||
source .env.local && go test -v ./tests/acceptance/...
|
||||
|
||||
# 运行特定功能的验收测试
|
||||
source .env.local && go test -v ./tests/acceptance/... -run TestPackage
|
||||
|
||||
# 运行特定场景
|
||||
source .env.local && go test -v ./tests/acceptance/... -run "Scenario_成功创建"
|
||||
```
|
||||
|
||||
## 测试环境
|
||||
|
||||
验收测试使用 `IntegrationTestEnv`,提供:
|
||||
|
||||
- **事务隔离**:每个测试在独立事务中运行,自动回滚
|
||||
- **Redis 清理**:测试前自动清理相关 Redis 键
|
||||
- **身份切换**:支持不同角色的请求
|
||||
|
||||
```go
|
||||
env := testutils.NewIntegrationTestEnv(t)
|
||||
|
||||
// 以超级管理员身份请求
|
||||
env.AsSuperAdmin().Request("GET", "/api/admin/xxx", nil)
|
||||
|
||||
// 以平台用户身份请求
|
||||
env.AsPlatformUser(accountID).Request("GET", "/api/admin/xxx", nil)
|
||||
|
||||
// 以代理商身份请求
|
||||
env.AsShopAgent(shopID).Request("GET", "/api/admin/xxx", nil)
|
||||
|
||||
// 以企业用户身份请求
|
||||
env.AsEnterprise(enterpriseID).Request("GET", "/api/admin/xxx", nil)
|
||||
```
|
||||
|
||||
## 与 Spec 的对应关系
|
||||
|
||||
```markdown
|
||||
# Spec 中的 Scenario
|
||||
|
||||
#### Scenario: 成功创建套餐
|
||||
- **GIVEN** 用户已登录且有创建权限
|
||||
- **WHEN** POST /api/admin/packages with valid data
|
||||
- **THEN** 返回 201 和套餐详情
|
||||
- **AND** 数据库中存在该套餐记录
|
||||
```
|
||||
|
||||
对应测试:
|
||||
|
||||
```go
|
||||
// 直接从 Spec Scenario 转换
|
||||
t.Run("Scenario_成功创建套餐", func(t *testing.T) {
|
||||
// GIVEN: 用户已登录且有创建权限
|
||||
client := env.AsSuperAdmin()
|
||||
|
||||
// WHEN: POST /api/admin/packages with valid data
|
||||
resp, err := client.Request("POST", "/api/admin/packages", validBody)
|
||||
|
||||
// THEN: 返回 201 和套餐详情
|
||||
assert.Equal(t, 201, resp.StatusCode)
|
||||
|
||||
// AND: 数据库中存在该套餐记录
|
||||
// 验证数据库状态
|
||||
})
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 验收测试和集成测试的区别?
|
||||
|
||||
| 方面 | 验收测试 | 集成测试 |
|
||||
|------|---------|---------|
|
||||
| 来源 | Spec Scenario | 开发者编写 |
|
||||
| 目的 | 验证 API 契约 | 验证系统集成 |
|
||||
| 粒度 | 单 API | 可能涉及多 API |
|
||||
| 时机 | 实现前生成 | 实现后编写 |
|
||||
|
||||
### Q: 测试 PASS 了但功能还没实现?
|
||||
|
||||
说明测试写得太弱。检查:
|
||||
1. 是否验证了响应状态码
|
||||
2. 是否验证了响应体结构
|
||||
3. 是否验证了数据库状态变化
|
||||
4. 破坏点是否准确
|
||||
|
||||
### Q: 如何处理需要前置数据的测试?
|
||||
|
||||
在 GIVEN 阶段创建必要的前置数据:
|
||||
|
||||
```go
|
||||
t.Run("Scenario_删除已分配的套餐失败", func(t *testing.T) {
|
||||
// GIVEN: 存在一个已分配给店铺的套餐
|
||||
client := env.AsSuperAdmin()
|
||||
|
||||
// 创建套餐
|
||||
createResp, _ := client.Request("POST", "/api/admin/packages", packageBody)
|
||||
var createResult map[string]interface{}
|
||||
createResp.JSON(&createResult)
|
||||
packageID := uint(createResult["data"].(map[string]interface{})["id"].(float64))
|
||||
|
||||
// 分配给店铺
|
||||
client.Request("POST", "/api/admin/shop-packages", map[string]interface{}{
|
||||
"package_id": packageID,
|
||||
"shop_id": 1,
|
||||
})
|
||||
|
||||
// WHEN: 尝试删除套餐
|
||||
resp, _ := client.Request("DELETE", fmt.Sprintf("/api/admin/packages/%d", packageID), nil)
|
||||
|
||||
// THEN: 返回 400
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
})
|
||||
```
|
||||
444
tests/acceptance/commission_calculation_acceptance_test.go
Normal file
444
tests/acceptance/commission_calculation_acceptance_test.go
Normal file
@@ -0,0 +1,444 @@
|
||||
package acceptance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// 验收测试:佣金计算重构
|
||||
// 来源:openspec/changes/refactor-one-time-commission-allocation/specs/commission-calculation/spec.md
|
||||
// 来源:openspec/changes/refactor-one-time-commission-allocation/specs/commission-trigger/spec.md
|
||||
// ============================================================
|
||||
|
||||
func TestCommissionCalculation_SeriesAllocationQuery_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
childShop := env.CreateTestShop("二级代理", 2, &parentShop.ID)
|
||||
series := createCommissionTestSeries(t, env, "佣金测试系列")
|
||||
|
||||
createPlatformSeriesAllocationForCommission(t, env, parentShop.ID, series.ID, 10000)
|
||||
createSeriesAllocationForCommission(t, env, parentShop.ID, childShop.ID, series.ID, 5000)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 直接查询系列分配
|
||||
// GIVEN: 存在 shop_id + series_id 的系列分配记录
|
||||
// WHEN: 通过 shop_id 和 series_id 查询
|
||||
// THEN: 返回唯一匹配的记录,包含 one_time_commission_amount
|
||||
//
|
||||
// 破坏点:如果查询 API 不支持 series_id 筛选,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_直接查询系列分配", func(t *testing.T) {
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations?shop_id=%d&series_id=%d",
|
||||
childShop.ID, series.ID)
|
||||
|
||||
resp, err := env.AsSuperAdmin().Request("GET", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
items := data["items"].([]interface{})
|
||||
require.Len(t, items, 1, "应返回唯一匹配记录")
|
||||
|
||||
allocation := items[0].(map[string]interface{})
|
||||
assert.Equal(t, float64(5000), allocation["one_time_commission_amount"],
|
||||
"佣金金额应为 5000 分")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 系列分配不存在
|
||||
// GIVEN: shop_id + series_id 组合不存在分配记录
|
||||
// WHEN: 查询该组合
|
||||
// THEN: 返回空列表
|
||||
//
|
||||
// 破坏点:如果查询不正确处理空结果,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_系列分配不存在", func(t *testing.T) {
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations?shop_id=%d&series_id=99999",
|
||||
childShop.ID)
|
||||
|
||||
resp, err := env.AsSuperAdmin().Request("GET", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
list := data["items"].([]interface{})
|
||||
assert.Empty(t, list, "不存在的组合应返回空列表")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCommissionCalculation_EnableOneTimeCommission_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
series := createCommissionTestSeriesWithConfig(t, env, "启用佣金系列", true)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 检查系列是否启用一次性佣金
|
||||
// GIVEN: 系列配置 enable_one_time_commission = true
|
||||
// WHEN: 查询系列详情
|
||||
// THEN: 响应包含 enable_one_time_commission = true
|
||||
//
|
||||
// 破坏点:如果系列 API 不返回 enable_one_time_commission 字段,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_检查系列是否启用一次性佣金", func(t *testing.T) {
|
||||
path := fmt.Sprintf("/api/admin/package-series/%d", series.ID)
|
||||
|
||||
resp, err := env.AsSuperAdmin().Request("GET", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
enableOneTime, ok := data["enable_one_time_commission"]
|
||||
assert.True(t, ok, "响应应包含 enable_one_time_commission 字段")
|
||||
assert.Equal(t, true, enableOneTime, "应为 true")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 批量查询启用一次性佣金的系列
|
||||
// GIVEN: 存在多个系列,部分启用一次性佣金
|
||||
// WHEN: 查询系列列表并按 enable_one_time_commission 筛选
|
||||
// THEN: 返回符合条件的系列
|
||||
//
|
||||
// 破坏点:如果不支持 enable_one_time_commission 筛选,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_批量查询启用一次性佣金的系列", func(t *testing.T) {
|
||||
createCommissionTestSeriesWithConfig(t, env, "禁用佣金系列", false)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("GET", "/api/admin/package-series?enable_one_time_commission=true", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
list := data["items"].([]interface{})
|
||||
|
||||
for _, item := range list {
|
||||
seriesItem := item.(map[string]interface{})
|
||||
enableVal, hasField := seriesItem["enable_one_time_commission"]
|
||||
if hasField {
|
||||
assert.Equal(t, true, enableVal, "筛选结果应全部为启用状态")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCommissionCalculation_ChainAllocation_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
level1Shop := env.CreateTestShop("一级代理", 1, nil)
|
||||
level2Shop := env.CreateTestShop("二级代理", 2, &level1Shop.ID)
|
||||
level3Shop := env.CreateTestShop("三级代理", 3, &level2Shop.ID)
|
||||
series := createCommissionTestSeries(t, env, "链式分配系列")
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 链式分配金额计算
|
||||
// GIVEN:
|
||||
// - 平台给一级:one_time_commission_amount = 10000(100元)
|
||||
// - 一级给二级:one_time_commission_amount = 8000(80元)
|
||||
// - 二级给三级:one_time_commission_amount = 5000(50元)
|
||||
// WHEN: 查询各级的系列分配
|
||||
// THEN: 各级金额正确
|
||||
//
|
||||
// 破坏点:如果分配金额不正确保存,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_链式分配金额计算", func(t *testing.T) {
|
||||
createPlatformSeriesAllocationForCommission(t, env, level1Shop.ID, series.ID, 10000)
|
||||
createSeriesAllocationForCommission(t, env, level1Shop.ID, level2Shop.ID, series.ID, 8000)
|
||||
createSeriesAllocationForCommission(t, env, level2Shop.ID, level3Shop.ID, series.ID, 5000)
|
||||
|
||||
verifyAllocationAmount(t, env, level1Shop.ID, series.ID, 10000)
|
||||
verifyAllocationAmount(t, env, level2Shop.ID, series.ID, 8000)
|
||||
verifyAllocationAmount(t, env, level3Shop.ID, series.ID, 5000)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 单级代理
|
||||
// GIVEN: 一级代理直接销售(无下级)
|
||||
// WHEN: 查询一级的系列分配
|
||||
// THEN: 一级获得完整的 one_time_commission_amount
|
||||
//
|
||||
// 破坏点:如果单级分配不生效,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_单级代理", func(t *testing.T) {
|
||||
singleShop := env.CreateTestShop("单级代理", 1, nil)
|
||||
singleSeries := createCommissionTestSeries(t, env, "单级系列")
|
||||
|
||||
createPlatformSeriesAllocationForCommission(t, env, singleShop.ID, singleSeries.ID, 10000)
|
||||
verifyAllocationAmount(t, env, singleShop.ID, singleSeries.ID, 10000)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCommissionCalculation_TriggerConfig_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
childShop := env.CreateTestShop("二级代理", 2, &parentShop.ID)
|
||||
series := createCommissionTestSeries(t, env, "触发配置系列")
|
||||
|
||||
createPlatformSeriesAllocationForCommission(t, env, parentShop.ID, series.ID, 10000)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 累计达到阈值触发佣金配置
|
||||
// GIVEN: 系列分配设置为累计充值触发,阈值 1000 元
|
||||
// WHEN: 创建系列分配
|
||||
// THEN: 配置正确保存
|
||||
//
|
||||
// 破坏点:如果触发配置不保存,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_累计达到阈值触发佣金配置", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"shop_id": childShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 5000,
|
||||
"enable_one_time_commission": true,
|
||||
"one_time_commission_trigger": "accumulated_recharge",
|
||||
"one_time_commission_threshold": 100000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, true, data["enable_one_time_commission"])
|
||||
assert.Equal(t, "accumulated_recharge", data["one_time_commission_trigger"])
|
||||
assert.Equal(t, float64(100000), data["one_time_commission_threshold"])
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 首次充值触发配置
|
||||
// GIVEN: 系列分配设置为首次充值触发,阈值 100 元
|
||||
// WHEN: 创建系列分配
|
||||
// THEN: 配置正确保存
|
||||
//
|
||||
// 破坏点:如果 first_recharge 触发类型不支持,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_首次充值触发配置", func(t *testing.T) {
|
||||
newChildShop := env.CreateTestShop("首充测试店铺", 2, &parentShop.ID)
|
||||
|
||||
body := map[string]interface{}{
|
||||
"shop_id": newChildShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 5000,
|
||||
"enable_one_time_commission": true,
|
||||
"one_time_commission_trigger": "first_recharge",
|
||||
"one_time_commission_threshold": 10000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, "first_recharge", data["one_time_commission_trigger"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestCommissionStats_Allocation_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
series := createCommissionTestSeries(t, env, "统计测试系列")
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 创建佣金统计记录关联系列分配
|
||||
// GIVEN: 存在系列分配记录
|
||||
// WHEN: 查询佣金统计
|
||||
// THEN: 统计记录的 allocation_id 指向 ShopSeriesAllocation.id
|
||||
//
|
||||
// 破坏点:如果统计不关联系列分配,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_佣金统计关联系列分配", func(t *testing.T) {
|
||||
allocation := createPlatformSeriesAllocationForCommission(t, env, parentShop.ID, series.ID, 10000)
|
||||
|
||||
path := fmt.Sprintf("/api/admin/shop-series-commission-stats?shop_id=%d&series_id=%d",
|
||||
parentShop.ID, series.ID)
|
||||
|
||||
resp, err := env.AsSuperAdmin().Request("GET", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
if result.Code == 0 && result.Data != nil {
|
||||
data := result.Data.(map[string]interface{})
|
||||
if list, ok := data["items"].([]interface{}); ok && len(list) > 0 {
|
||||
stats := list[0].(map[string]interface{})
|
||||
if allocationID, exists := stats["allocation_id"]; exists {
|
||||
assert.Equal(t, float64(allocation.ID), allocationID,
|
||||
"统计应关联到系列分配 ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 辅助函数
|
||||
// ============================================================
|
||||
|
||||
func createCommissionTestSeries(t *testing.T, env *integ.IntegrationTestEnv, name string) *model.PackageSeries {
|
||||
t.Helper()
|
||||
|
||||
timestamp := time.Now().UnixNano()
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: fmt.Sprintf("COMM_SERIES_%d", timestamp),
|
||||
SeriesName: name,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(series).Error
|
||||
require.NoError(t, err, "创建测试系列失败")
|
||||
|
||||
return series
|
||||
}
|
||||
|
||||
func createCommissionTestSeriesWithConfig(t *testing.T, env *integ.IntegrationTestEnv, name string, enableOneTime bool) *model.PackageSeries {
|
||||
t.Helper()
|
||||
|
||||
timestamp := time.Now().UnixNano()
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: fmt.Sprintf("COMM_SERIES_%d", timestamp),
|
||||
SeriesName: name,
|
||||
Status: constants.StatusEnabled,
|
||||
EnableOneTimeCommission: enableOneTime,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(series).Error
|
||||
require.NoError(t, err, "创建测试系列失败")
|
||||
|
||||
return series
|
||||
}
|
||||
|
||||
func createPlatformSeriesAllocationForCommission(t *testing.T, env *integ.IntegrationTestEnv, shopID, seriesID uint, amount int64) *model.ShopSeriesAllocation {
|
||||
t.Helper()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: shopID,
|
||||
SeriesID: seriesID,
|
||||
AllocatorShopID: 0,
|
||||
OneTimeCommissionAmount: amount,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(allocation).Error
|
||||
require.NoError(t, err, "创建平台系列分配失败")
|
||||
|
||||
return allocation
|
||||
}
|
||||
|
||||
func createSeriesAllocationForCommission(t *testing.T, env *integ.IntegrationTestEnv, allocatorShopID, shopID, seriesID uint, amount int64) *model.ShopSeriesAllocation {
|
||||
t.Helper()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: shopID,
|
||||
SeriesID: seriesID,
|
||||
AllocatorShopID: allocatorShopID,
|
||||
OneTimeCommissionAmount: amount,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(allocation).Error
|
||||
require.NoError(t, err, "创建系列分配失败")
|
||||
|
||||
return allocation
|
||||
}
|
||||
|
||||
func verifyAllocationAmount(t *testing.T, env *integ.IntegrationTestEnv, shopID, seriesID uint, expectedAmount int64) {
|
||||
t.Helper()
|
||||
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations?shop_id=%d&series_id=%d", shopID, seriesID)
|
||||
|
||||
resp, err := env.AsSuperAdmin().Request("GET", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
list := data["items"].([]interface{})
|
||||
require.NotEmpty(t, list, "应存在分配记录")
|
||||
|
||||
allocation := list[0].(map[string]interface{})
|
||||
assert.Equal(t, float64(expectedAmount), allocation["one_time_commission_amount"],
|
||||
"店铺 %d 系列 %d 的佣金金额应为 %d", shopID, seriesID, expectedAmount)
|
||||
}
|
||||
847
tests/acceptance/shop_series_allocation_acceptance_test.go
Normal file
847
tests/acceptance/shop_series_allocation_acceptance_test.go
Normal file
@@ -0,0 +1,847 @@
|
||||
package acceptance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// 验收测试:套餐系列分配 (ShopSeriesAllocation)
|
||||
// 来源:openspec/changes/refactor-one-time-commission-allocation/specs/shop-series-allocation/spec.md
|
||||
// ============================================================
|
||||
|
||||
func TestShopSeriesAllocation_Create_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
// 准备测试数据:创建店铺层级和套餐系列
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
childShop := env.CreateTestShop("二级代理", 2, &parentShop.ID)
|
||||
series := createTestPackageSeries(t, env, "测试系列")
|
||||
|
||||
// 先为一级代理创建系列分配(平台分配)
|
||||
platformAllocation := createPlatformSeriesAllocation(t, env, parentShop.ID, series.ID, 10000)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 成功分配套餐系列
|
||||
// GIVEN: 代理已有该系列的分配权限
|
||||
// WHEN: POST /api/admin/shop-series-allocations 设置 one_time_commission_amount = 5000
|
||||
// THEN: 返回 200 和分配记录详情
|
||||
// AND: 下级代理的一次性佣金上限为 50 元
|
||||
//
|
||||
// 破坏点:如果 Handler 不调用 Service.Create,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_成功分配套餐系列", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"shop_id": childShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 5000, // 50 元
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code, "应返回成功: %s", result.Message)
|
||||
|
||||
// 验证响应包含 one_time_commission_amount
|
||||
data, ok := result.Data.(map[string]interface{})
|
||||
require.True(t, ok, "响应 data 应为对象")
|
||||
assert.Equal(t, float64(5000), data["one_time_commission_amount"], "一次性佣金金额应为 5000 分")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 下级金额不能超过上级
|
||||
// GIVEN: 上级分配金额为 10000 分(100 元)
|
||||
// WHEN: 尝试为下级分配 15000 分(150 元)
|
||||
// THEN: 返回 400 错误 "一次性佣金金额不能超过您的分配上限"
|
||||
//
|
||||
// 破坏点:如果移除金额上限校验,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_下级金额不能超过上级", func(t *testing.T) {
|
||||
newChildShop := env.CreateTestShop("新下级店铺", 2, &parentShop.ID)
|
||||
|
||||
body := map[string]interface{}{
|
||||
"shop_id": newChildShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 15000, // 超过上级的 10000
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, 0, result.Code, "应返回错误")
|
||||
assert.Contains(t, result.Message, "超过", "错误消息应包含'超过'")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 分配时启用一次性佣金和强充
|
||||
// GIVEN: 代理有分配权限
|
||||
// WHEN: POST 创建分配,启用一次性佣金(累计充值触发,阈值 1000 元),启用强充(100 元)
|
||||
// THEN: 系统保存完整配置
|
||||
//
|
||||
// 破坏点:如果不保存 enable_one_time_commission 等字段,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_分配时启用一次性佣金和强充", func(t *testing.T) {
|
||||
newChildShop := env.CreateTestShop("新下级店铺2", 2, &parentShop.ID)
|
||||
|
||||
body := map[string]interface{}{
|
||||
"shop_id": newChildShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 5000,
|
||||
"enable_one_time_commission": true,
|
||||
"one_time_commission_trigger": "accumulated_recharge",
|
||||
"one_time_commission_threshold": 100000, // 1000 元
|
||||
"enable_force_recharge": true,
|
||||
"force_recharge_amount": 10000, // 100 元
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, true, data["enable_one_time_commission"])
|
||||
assert.Equal(t, "accumulated_recharge", data["one_time_commission_trigger"])
|
||||
assert.Equal(t, float64(100000), data["one_time_commission_threshold"])
|
||||
assert.Equal(t, true, data["enable_force_recharge"])
|
||||
assert.Equal(t, float64(10000), data["force_recharge_amount"])
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 尝试分配未拥有的系列
|
||||
// GIVEN: 代理没有某系列的分配权限
|
||||
// WHEN: 尝试为下级分配该系列
|
||||
// THEN: 返回 403/400 "您没有该套餐系列的分配权限"
|
||||
//
|
||||
// 破坏点:如果不检查代理是否拥有系列权限,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_尝试分配未拥有的系列", func(t *testing.T) {
|
||||
unownedSeries := createTestPackageSeries(t, env, "未分配系列")
|
||||
|
||||
body := map[string]interface{}{
|
||||
"shop_id": childShop.ID,
|
||||
"series_id": unownedSeries.ID,
|
||||
"one_time_commission_amount": 5000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 应返回 400 或 403
|
||||
assert.True(t, resp.StatusCode == 400 || resp.StatusCode == 403,
|
||||
"应返回 400 或 403,实际: %d", resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, 0, result.Code)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 尝试分配给非直属下级
|
||||
// GIVEN: 店铺 A 是一级,店铺 B 是二级(A 的下级),店铺 C 是三级(B 的下级)
|
||||
// WHEN: 店铺 A 尝试直接分配给店铺 C
|
||||
// THEN: 返回 403 "只能为直属下级分配套餐"
|
||||
//
|
||||
// 破坏点:如果不检查是否为直属下级,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_尝试分配给非直属下级", func(t *testing.T) {
|
||||
grandChildShop := env.CreateTestShop("三级代理", 3, &childShop.ID)
|
||||
|
||||
body := map[string]interface{}{
|
||||
"shop_id": grandChildShop.ID, // 非直属下级
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 5000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.True(t, resp.StatusCode == 400 || resp.StatusCode == 403)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, 0, result.Code)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 重复分配同一系列
|
||||
// GIVEN: 已为下级店铺分配了某系列
|
||||
// WHEN: 再次尝试分配同一系列
|
||||
// THEN: 返回 409 "该店铺已分配此套餐系列"
|
||||
//
|
||||
// 破坏点:如果不检查唯一索引,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_重复分配同一系列", func(t *testing.T) {
|
||||
newChildShop := env.CreateTestShop("重复测试店铺", 2, &parentShop.ID)
|
||||
|
||||
// 第一次分配
|
||||
body := map[string]interface{}{
|
||||
"shop_id": newChildShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 5000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
resp.Body.Close()
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
// 第二次分配(应失败)
|
||||
resp2, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp2.Body.Close()
|
||||
|
||||
assert.True(t, resp2.StatusCode == 400 || resp2.StatusCode == 409,
|
||||
"重复分配应返回 400 或 409,实际: %d", resp2.StatusCode)
|
||||
})
|
||||
|
||||
_ = platformAllocation // 使用变量避免编译警告
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocation_List_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
childShop := env.CreateTestShop("二级代理", 2, &parentShop.ID)
|
||||
series := createTestPackageSeries(t, env, "列表测试系列")
|
||||
|
||||
// 创建分配记录
|
||||
createPlatformSeriesAllocation(t, env, parentShop.ID, series.ID, 10000)
|
||||
createSeriesAllocationDirectly(t, env, parentShop.ID, childShop.ID, series.ID, 5000)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 查询所有分配
|
||||
// GIVEN: 存在多条分配记录
|
||||
// WHEN: GET /api/admin/shop-series-allocations 不带筛选条件
|
||||
// THEN: 返回该代理创建的所有分配记录
|
||||
// AND: 每条记录包含 one_time_commission_amount 字段
|
||||
//
|
||||
// 破坏点:如果 List API 不返回数据,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_查询所有分配", func(t *testing.T) {
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("GET", "/api/admin/shop-series-allocations", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
// 验证返回列表格式
|
||||
data := result.Data.(map[string]interface{})
|
||||
items, ok := data["items"].([]interface{})
|
||||
require.True(t, ok, "响应应包含 items 字段")
|
||||
require.NotEmpty(t, items, "列表不应为空")
|
||||
|
||||
// 验证第一条记录包含 one_time_commission_amount
|
||||
firstItem := items[0].(map[string]interface{})
|
||||
_, hasAmount := firstItem["one_time_commission_amount"]
|
||||
assert.True(t, hasAmount, "记录应包含 one_time_commission_amount 字段")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 按店铺筛选
|
||||
// GIVEN: 存在多个店铺的分配记录
|
||||
// WHEN: GET /api/admin/shop-series-allocations?shop_id=xxx
|
||||
// THEN: 只返回该店铺的分配记录
|
||||
//
|
||||
// 破坏点:如果不支持 shop_id 筛选,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_按店铺筛选", func(t *testing.T) {
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations?shop_id=%d", childShop.ID)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("GET", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
// 验证筛选结果
|
||||
data := result.Data.(map[string]interface{})
|
||||
items := data["items"].([]interface{})
|
||||
for _, item := range items {
|
||||
record := item.(map[string]interface{})
|
||||
assert.Equal(t, float64(childShop.ID), record["shop_id"],
|
||||
"筛选结果应只包含指定店铺")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocation_Update_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
childShop := env.CreateTestShop("二级代理", 2, &parentShop.ID)
|
||||
series := createTestPackageSeries(t, env, "更新测试系列")
|
||||
|
||||
createPlatformSeriesAllocation(t, env, parentShop.ID, series.ID, 10000)
|
||||
allocation := createSeriesAllocationDirectly(t, env, parentShop.ID, childShop.ID, series.ID, 5000)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 更新一次性佣金金额
|
||||
// GIVEN: 存在一条分配记录,金额为 5000
|
||||
// WHEN: PUT /api/admin/shop-series-allocations/:id 将金额改为 6000
|
||||
// THEN: 更新成功,返回更新后的记录
|
||||
//
|
||||
// 破坏点:如果 Update API 不保存金额变更,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_更新一次性佣金金额", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"one_time_commission_amount": 6000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("PUT", path, jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, float64(6000), data["one_time_commission_amount"])
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 更新金额不能超过上级上限
|
||||
// GIVEN: 上级分配金额上限为 10000
|
||||
// WHEN: 尝试将金额更新为 15000
|
||||
// THEN: 返回 400 错误
|
||||
//
|
||||
// 破坏点:如果更新时不检查金额上限,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_更新金额不能超过上级上限", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"one_time_commission_amount": 15000, // 超过上级的 10000
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("PUT", path, jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 更新强充配置
|
||||
// GIVEN: 分配记录存在
|
||||
// WHEN: PUT 启用强充,设置金额 100 元
|
||||
// THEN: 配置更新成功
|
||||
//
|
||||
// 破坏点:如果不保存强充配置,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_更新强充配置", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"enable_force_recharge": true,
|
||||
"force_recharge_amount": 10000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("PUT", path, jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, true, data["enable_force_recharge"])
|
||||
assert.Equal(t, float64(10000), data["force_recharge_amount"])
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 更新不存在的分配
|
||||
// GIVEN: 分配 ID 不存在
|
||||
// WHEN: PUT /api/admin/shop-series-allocations/99999
|
||||
// THEN: 返回 404 "分配记录不存在"
|
||||
//
|
||||
// 破坏点:如果不检查记录是否存在,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_更新不存在的分配", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"one_time_commission_amount": 5000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("PUT", "/api/admin/shop-series-allocations/99999", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 404, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocation_Delete_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
childShop := env.CreateTestShop("二级代理", 2, &parentShop.ID)
|
||||
series := createTestPackageSeries(t, env, "删除测试系列")
|
||||
|
||||
createPlatformSeriesAllocation(t, env, parentShop.ID, series.ID, 10000)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 删除系列分配时检查套餐分配
|
||||
// GIVEN: 系列分配存在,且有依赖的套餐分配
|
||||
// WHEN: DELETE /api/admin/shop-series-allocations/:id
|
||||
// THEN: 返回 400 "存在关联的套餐分配,无法删除"
|
||||
//
|
||||
// 破坏点:如果不检查套餐分配依赖,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_删除系列分配时检查套餐分配", func(t *testing.T) {
|
||||
// 创建系列分配
|
||||
allocation := createSeriesAllocationDirectly(t, env, parentShop.ID, childShop.ID, series.ID, 5000)
|
||||
|
||||
// 创建依赖的套餐分配
|
||||
pkg := createTestPackage(t, env, series.ID, "测试套餐")
|
||||
createPackageAllocationWithSeriesAllocation(t, env, childShop.ID, pkg.ID, allocation.ID)
|
||||
|
||||
// 尝试删除系列分配
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("DELETE", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, result.Message, "关联", "错误消息应提及关联")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 成功删除无依赖的系列分配
|
||||
// GIVEN: 系列分配存在,无套餐分配依赖
|
||||
// WHEN: DELETE /api/admin/shop-series-allocations/:id
|
||||
// THEN: 删除成功
|
||||
//
|
||||
// 破坏点:如果 Delete API 不工作,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_成功删除无依赖的系列分配", func(t *testing.T) {
|
||||
newChildShop := env.CreateTestShop("新下级", 2, &parentShop.ID)
|
||||
allocation := createSeriesAllocationDirectly(t, env, parentShop.ID, newChildShop.ID, series.ID, 5000)
|
||||
|
||||
path := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
|
||||
resp, err := env.AsUser(createTestAgentAccount(t, env, parentShop.ID)).
|
||||
Request("DELETE", path, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocation_Platform_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
series := createTestPackageSeries(t, env, "平台分配测试系列")
|
||||
// 设置系列的一次性佣金上限(假设固定 150 元)
|
||||
setSeriesOneTimeCommissionLimit(t, env, series.ID, 15000)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 平台为一级代理分配
|
||||
// GIVEN: 平台管理员
|
||||
// WHEN: POST 为一级代理分配套餐系列,设置 one_time_commission_amount = 10000
|
||||
// THEN: 分配成功
|
||||
//
|
||||
// 破坏点:如果平台无法创建分配,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_平台为一级代理分配", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"shop_id": parentShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 10000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 平台可自由设定金额
|
||||
// GIVEN: 平台管理员
|
||||
// WHEN: 平台为一级代理分配任意金额(如 20000)
|
||||
// THEN: 分配成功(平台无上限限制)
|
||||
//
|
||||
// 破坏点:如果平台分配被上限限制,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_平台可自由设定金额", func(t *testing.T) {
|
||||
newShop := env.CreateTestShop("新一级代理", 1, nil)
|
||||
|
||||
body := map[string]interface{}{
|
||||
"shop_id": newShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 20000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 平台配置强充要求
|
||||
// GIVEN: 平台管理员
|
||||
// WHEN: POST 为一级代理分配系列,启用强充,force_recharge_amount = 10000
|
||||
// THEN: 配置保存成功
|
||||
//
|
||||
// 破坏点:如果不保存强充配置,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_平台配置强充要求", func(t *testing.T) {
|
||||
newShop := env.CreateTestShop("强充测试店铺", 1, nil)
|
||||
|
||||
body := map[string]interface{}{
|
||||
"shop_id": newShop.ID,
|
||||
"series_id": series.ID,
|
||||
"one_time_commission_amount": 10000,
|
||||
"enable_force_recharge": true,
|
||||
"force_recharge_amount": 10000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("POST", "/api/admin/shop-series-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, true, data["enable_force_recharge"])
|
||||
assert.Equal(t, float64(10000), data["force_recharge_amount"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopPackageAllocation_SeriesDependency_Acceptance(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
parentShop := env.CreateTestShop("一级代理", 1, nil)
|
||||
childShop := env.CreateTestShop("二级代理", 2, &parentShop.ID)
|
||||
series := createTestPackageSeries(t, env, "依赖测试系列")
|
||||
pkg := createTestPackage(t, env, series.ID, "依赖测试套餐")
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 未分配系列时分配套餐失败
|
||||
// GIVEN: 下级店铺未被分配系列 X
|
||||
// WHEN: 代理尝试为下级分配套餐 A(属于系列 X)
|
||||
// THEN: 返回 400 "请先分配该套餐所属的系列"
|
||||
//
|
||||
// 破坏点:如果不检查系列分配依赖,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_未分配系列时分配套餐失败", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"shop_id": childShop.ID,
|
||||
"package_id": pkg.ID,
|
||||
"cost_price": 5000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("POST", "/api/admin/shop-package-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, result.Message, "系列", "错误消息应提及系列")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 先分配系列再分配套餐
|
||||
// GIVEN: 下级店铺已被分配系列 X
|
||||
// WHEN: 代理为下级分配套餐 A(属于系列 X)
|
||||
// THEN: 分配成功,套餐分配关联到系列分配记录
|
||||
//
|
||||
// 破坏点:如果不关联 series_allocation_id,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_先分配系列再分配套餐", func(t *testing.T) {
|
||||
// 先分配系列
|
||||
createPlatformSeriesAllocation(t, env, parentShop.ID, series.ID, 10000)
|
||||
seriesAllocation := createSeriesAllocationDirectly(t, env, parentShop.ID, childShop.ID, series.ID, 5000)
|
||||
|
||||
// 再分配套餐
|
||||
body := map[string]interface{}{
|
||||
"shop_id": childShop.ID,
|
||||
"package_id": pkg.ID,
|
||||
"cost_price": 5000,
|
||||
}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("POST", "/api/admin/shop-package-allocations", jsonBody)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
// 验证关联
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, float64(seriesAllocation.ID), data["series_allocation_id"],
|
||||
"套餐分配应关联到系列分配")
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Scenario: 套餐分配只包含成本价
|
||||
// GIVEN: 套餐分配 API
|
||||
// WHEN: 创建或查询套餐分配
|
||||
// THEN: 请求/响应只包含 cost_price,不包含 one_time_commission_amount
|
||||
//
|
||||
// 破坏点:如果响应包含 one_time_commission_amount,此测试将失败
|
||||
// ------------------------------------------------------------
|
||||
t.Run("Scenario_套餐分配只包含成本价", func(t *testing.T) {
|
||||
// 查询已创建的套餐分配
|
||||
resp, err := env.AsSuperAdmin().
|
||||
Request("GET", fmt.Sprintf("/api/admin/shop-package-allocations?shop_id=%d", childShop.ID), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
items := data["items"].([]interface{})
|
||||
if len(items) > 0 {
|
||||
firstItem := items[0].(map[string]interface{})
|
||||
_, hasCostPrice := firstItem["cost_price"]
|
||||
_, hasOneTimeCommission := firstItem["one_time_commission_amount"]
|
||||
|
||||
assert.True(t, hasCostPrice, "应包含 cost_price")
|
||||
assert.False(t, hasOneTimeCommission, "不应包含 one_time_commission_amount")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 辅助函数
|
||||
// ============================================================
|
||||
|
||||
func createTestPackageSeries(t *testing.T, env *integ.IntegrationTestEnv, name string) *model.PackageSeries {
|
||||
t.Helper()
|
||||
|
||||
timestamp := time.Now().UnixNano()
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: fmt.Sprintf("SERIES_%d", timestamp),
|
||||
SeriesName: name,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(series).Error
|
||||
require.NoError(t, err, "创建测试套餐系列失败")
|
||||
|
||||
return series
|
||||
}
|
||||
|
||||
func createTestPackage(t *testing.T, env *integ.IntegrationTestEnv, seriesID uint, name string) *model.Package {
|
||||
t.Helper()
|
||||
|
||||
timestamp := time.Now().UnixNano()
|
||||
pkg := &model.Package{
|
||||
PackageCode: fmt.Sprintf("PKG_%d", timestamp),
|
||||
PackageName: name,
|
||||
SeriesID: seriesID,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
CostPrice: 5000,
|
||||
SuggestedRetailPrice: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(pkg).Error
|
||||
require.NoError(t, err, "创建测试套餐失败")
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func createTestAgentAccount(t *testing.T, env *integ.IntegrationTestEnv, shopID uint) *model.Account {
|
||||
t.Helper()
|
||||
return env.CreateTestAccount("agent", "password123", constants.UserTypeAgent, &shopID, nil)
|
||||
}
|
||||
|
||||
// createPlatformSeriesAllocation 模拟平台为一级代理创建的系列分配
|
||||
// 注意:由于 ShopSeriesAllocation 模型可能尚未创建,这里直接通过数据库操作模拟
|
||||
func createPlatformSeriesAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, seriesID uint, amount int64) *model.ShopSeriesAllocation {
|
||||
t.Helper()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: shopID,
|
||||
SeriesID: seriesID,
|
||||
AllocatorShopID: 0, // 平台分配
|
||||
OneTimeCommissionAmount: amount,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(allocation).Error
|
||||
require.NoError(t, err, "创建平台系列分配失败")
|
||||
|
||||
return allocation
|
||||
}
|
||||
|
||||
// createSeriesAllocationDirectly 直接在数据库创建系列分配记录
|
||||
func createSeriesAllocationDirectly(t *testing.T, env *integ.IntegrationTestEnv, allocatorShopID, shopID, seriesID uint, amount int64) *model.ShopSeriesAllocation {
|
||||
t.Helper()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: shopID,
|
||||
SeriesID: seriesID,
|
||||
AllocatorShopID: allocatorShopID,
|
||||
OneTimeCommissionAmount: amount,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(allocation).Error
|
||||
require.NoError(t, err, "创建系列分配失败")
|
||||
|
||||
return allocation
|
||||
}
|
||||
|
||||
// createPackageAllocationWithSeriesAllocation 创建关联系列分配的套餐分配
|
||||
func createPackageAllocationWithSeriesAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, packageID, seriesAllocationID uint) *model.ShopPackageAllocation {
|
||||
t.Helper()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: shopID,
|
||||
PackageID: packageID,
|
||||
SeriesAllocationID: &seriesAllocationID,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := env.TX.Create(allocation).Error
|
||||
require.NoError(t, err, "创建套餐分配失败")
|
||||
|
||||
return allocation
|
||||
}
|
||||
|
||||
// setSeriesOneTimeCommissionLimit 设置系列的一次性佣金上限(假设在 PackageSeries 或配置中)
|
||||
func setSeriesOneTimeCommissionLimit(t *testing.T, env *integ.IntegrationTestEnv, seriesID uint, limit int64) {
|
||||
t.Helper()
|
||||
|
||||
// 更新系列配置
|
||||
err := env.TX.Model(&model.PackageSeries{}).Where("id = ?", seriesID).Updates(map[string]interface{}{
|
||||
"enable_one_time_commission": true,
|
||||
// 假设有 one_time_commission_config 字段存储配置
|
||||
}).Error
|
||||
require.NoError(t, err, "设置系列佣金上限失败")
|
||||
}
|
||||
Reference in New Issue
Block a user