feat: 实现门店套餐分配功能并统一测试基础设施
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m30s

新增功能:
- 门店套餐分配管理(shop_package_allocation):支持门店套餐库存管理
- 门店套餐系列分配管理(shop_series_allocation):支持套餐系列分配和佣金层级设置
- 我的套餐查询(my_package):支持门店查询自己的套餐分配情况

测试改进:
- 统一集成测试基础设施,新增 testutils.NewIntegrationTestEnv
- 重构所有集成测试使用新的测试环境设置
- 移除旧的测试辅助函数和冗余测试文件
- 新增 test_helpers_test.go 统一任务测试辅助

技术细节:
- 新增数据库迁移 000025_create_shop_allocation_tables
- 新增 3 个 Handler、Service、Store 和对应的单元测试
- 更新 OpenAPI 文档和文档生成器
- 测试覆盖率:Service 层 > 90%

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 10:45:16 +08:00
parent 5fefe9d0cb
commit 23eb0307bb
73 changed files with 8716 additions and 4558 deletions

View File

@@ -2,16 +2,17 @@ package integration
import (
"context"
"os"
"testing"
"time"
"github.com/bytedance/sonic"
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/tests/testutils"
)
type EmailPayload struct {
@@ -22,22 +23,29 @@ type EmailPayload struct {
CC []string `json:"cc,omitempty"`
}
func getRedisOpt() asynq.RedisClientOpt {
host := os.Getenv("JUNHONG_REDIS_ADDRESS")
if host == "" {
host = "localhost"
}
port := os.Getenv("JUNHONG_REDIS_PORT")
if port == "" {
port = "6379"
}
password := os.Getenv("JUNHONG_REDIS_PASSWORD")
return asynq.RedisClientOpt{
Addr: host + ":" + port,
Password: password,
DB: 0,
}
}
func TestTaskSubmit(t *testing.T) {
rdb := redis.NewClient(&redis.Options{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
defer func() { _ = rdb.Close() }()
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
_ = rdb
ctx := context.Background()
cleanTestKeys(t, rdb, ctx)
client := asynq.NewClient(asynq.RedisClientOpt{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
client := asynq.NewClient(getRedisOpt())
defer func() { _ = client.Close() }()
// 构造任务载荷
@@ -66,21 +74,10 @@ func TestTaskSubmit(t *testing.T) {
}
func TestTaskPriority(t *testing.T) {
rdb := redis.NewClient(&redis.Options{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
defer func() { _ = rdb.Close() }()
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
ctx := context.Background()
cleanTestKeys(t, rdb, ctx)
client := asynq.NewClient(asynq.RedisClientOpt{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
client := asynq.NewClient(getRedisOpt())
defer func() { _ = client.Close() }()
tests := []struct {
@@ -114,21 +111,10 @@ func TestTaskPriority(t *testing.T) {
}
func TestTaskRetry(t *testing.T) {
rdb := redis.NewClient(&redis.Options{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
defer func() { _ = rdb.Close() }()
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
ctx := context.Background()
cleanTestKeys(t, rdb, ctx)
client := asynq.NewClient(asynq.RedisClientOpt{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
client := asynq.NewClient(getRedisOpt())
defer func() { _ = client.Close() }()
payload := &EmailPayload{
@@ -154,15 +140,10 @@ func TestTaskRetry(t *testing.T) {
}
func TestTaskIdempotency(t *testing.T) {
rdb := redis.NewClient(&redis.Options{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
defer func() { _ = rdb.Close() }()
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
ctx := context.Background()
cleanTestKeys(t, rdb, ctx)
requestID := "idempotent-test-" + time.Now().Format("20060102150405.000")
lockKey := constants.RedisTaskLockKey(requestID)
@@ -191,15 +172,10 @@ func TestTaskIdempotency(t *testing.T) {
}
func TestTaskStatusTracking(t *testing.T) {
rdb := redis.NewClient(&redis.Options{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
defer func() { _ = rdb.Close() }()
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
ctx := context.Background()
cleanTestKeys(t, rdb, ctx)
taskID := "task-123456"
statusKey := constants.RedisTaskStatusKey(taskID)
@@ -224,24 +200,20 @@ func TestTaskStatusTracking(t *testing.T) {
}
func TestQueueInspection(t *testing.T) {
rdb := redis.NewClient(&redis.Options{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
defer func() { _ = rdb.Close() }()
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
ctx := context.Background()
cleanTestKeys(t, rdb, ctx)
inspector := asynq.NewInspector(getRedisOpt())
defer func() { _ = inspector.Close() }()
client := asynq.NewClient(asynq.RedisClientOpt{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
_, _ = inspector.DeleteAllPendingTasks(constants.QueueDefault)
_, _ = inspector.DeleteAllScheduledTasks(constants.QueueDefault)
_, _ = inspector.DeleteAllRetryTasks(constants.QueueDefault)
_, _ = inspector.DeleteAllArchivedTasks(constants.QueueDefault)
client := asynq.NewClient(getRedisOpt())
defer func() { _ = client.Close() }()
// 提交多个任务
for i := 0; i < 5; i++ {
payload := &EmailPayload{
RequestID: "test-" + string(rune(i)),
@@ -258,14 +230,6 @@ func TestQueueInspection(t *testing.T) {
require.NoError(t, err)
}
inspector := asynq.NewInspector(asynq.RedisClientOpt{
Addr: testRedisAddr,
Password: testRedisPasswd,
DB: testRedisDB,
})
defer func() { _ = inspector.Close() }()
// 获取队列信息
info, err := inspector.GetQueueInfo(constants.QueueDefault)
require.NoError(t, err)
assert.Equal(t, 5, info.Pending)
@@ -316,19 +280,3 @@ func TestTaskSerialization(t *testing.T) {
})
}
}
func cleanTestKeys(t *testing.T, rdb *redis.Client, ctx context.Context) {
t.Helper()
prefix := "test:task:" + t.Name() + ":"
keys, err := rdb.Keys(ctx, prefix+"*").Result()
if err != nil {
return
}
if len(keys) > 0 {
rdb.Del(ctx, keys...)
}
asynqKeys, _ := rdb.Keys(ctx, "asynq:*").Result()
if len(asynqKeys) > 0 {
rdb.Del(ctx, asynqKeys...)
}
}