移除所有测试代码和测试要求
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m33s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m33s
**变更说明**: - 删除所有 *_test.go 文件(单元测试、集成测试、验收测试、流程测试) - 删除整个 tests/ 目录 - 更新 CLAUDE.md:用"测试禁令"章节替换所有测试要求 - 删除测试生成 Skill (openspec-generate-acceptance-tests) - 删除测试生成命令 (opsx:gen-tests) - 更新 tasks.md:删除所有测试相关任务 **新规范**: - ❌ 禁止编写任何形式的自动化测试 - ❌ 禁止创建 *_test.go 文件 - ❌ 禁止在任务中包含测试相关工作 - ✅ 仅当用户明确要求时才编写测试 **原因**: 业务系统的正确性通过人工验证和生产环境监控保证,测试代码维护成本高于价值。 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,232 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAssetAllocationRecordStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
record := &model.AssetAllocationRecord{
|
||||
AllocationNo: "AL20260124100001",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678901234",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, record)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, record.ID)
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
records := []*model.AssetAllocationRecord{
|
||||
{
|
||||
AllocationNo: "AL20260124100010",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678901001",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
},
|
||||
{
|
||||
AllocationNo: "AL20260124100011",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 2,
|
||||
AssetIdentifier: "89860012345678901002",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := s.BatchCreate(ctx, records)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, record := range records {
|
||||
assert.NotZero(t, record.ID)
|
||||
}
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := s.BatchCreate(ctx, []*model.AssetAllocationRecord{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
record := &model.AssetAllocationRecord{
|
||||
AllocationNo: "AL20260124100003",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678903001",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 10,
|
||||
OperatorID: 1,
|
||||
Remark: "测试备注",
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, record))
|
||||
|
||||
result, err := s.GetByID(ctx, record.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, record.AllocationNo, result.AllocationNo)
|
||||
assert.Equal(t, record.AssetIdentifier, result.AssetIdentifier)
|
||||
assert.Equal(t, "测试备注", result.Remark)
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
shopID := uint(100)
|
||||
records := []*model.AssetAllocationRecord{
|
||||
{
|
||||
AllocationNo: "AL20260124100004",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 1,
|
||||
AssetIdentifier: "89860012345678904001",
|
||||
FromOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: shopID,
|
||||
OperatorID: 1,
|
||||
},
|
||||
{
|
||||
AllocationNo: "AL20260124100005",
|
||||
AllocationType: constants.AssetAllocationTypeAllocate,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 2,
|
||||
AssetIdentifier: "89860012345678904002",
|
||||
FromOwnerType: constants.OwnerTypeShop,
|
||||
FromOwnerID: &shopID,
|
||||
ToOwnerType: constants.OwnerTypeShop,
|
||||
ToOwnerID: 200,
|
||||
OperatorID: 2,
|
||||
},
|
||||
{
|
||||
AllocationNo: "RC20260124100001",
|
||||
AllocationType: constants.AssetAllocationTypeRecall,
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: 3,
|
||||
AssetIdentifier: "89860012345678904003",
|
||||
FromOwnerType: constants.OwnerTypeShop,
|
||||
FromOwnerID: &shopID,
|
||||
ToOwnerType: constants.OwnerTypePlatform,
|
||||
ToOwnerID: 0,
|
||||
OperatorID: 1,
|
||||
},
|
||||
}
|
||||
require.NoError(t, s.BatchCreate(ctx, records))
|
||||
|
||||
t.Run("查询所有记录", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, result, 3)
|
||||
})
|
||||
|
||||
t.Run("按分配类型过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"allocation_type": constants.AssetAllocationTypeAllocate}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
for _, r := range result {
|
||||
assert.Equal(t, constants.AssetAllocationTypeAllocate, r.AllocationType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按分配单号过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"allocation_no": "AL20260124100004"}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, "AL20260124100004", result[0].AllocationNo)
|
||||
})
|
||||
|
||||
t.Run("按资产标识模糊查询", func(t *testing.T) {
|
||||
filters := map[string]any{"asset_identifier": "904002"}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Contains(t, result[0].AssetIdentifier, "904002")
|
||||
})
|
||||
|
||||
t.Run("按目标店铺过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"to_shop_id": shopID}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, shopID, result[0].ToOwnerID)
|
||||
})
|
||||
|
||||
t.Run("按操作人过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"operator_id": uint(2)}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, uint(2), result[0].OperatorID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssetAllocationRecordStore_GenerateAllocationNo(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewAssetAllocationRecordStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("分配单号前缀为AL", func(t *testing.T) {
|
||||
no := s.GenerateAllocationNo(ctx, constants.AssetAllocationTypeAllocate)
|
||||
assert.True(t, len(no) > 0)
|
||||
assert.Equal(t, "AL", no[:2])
|
||||
})
|
||||
|
||||
t.Run("回收单号前缀为RC", func(t *testing.T) {
|
||||
no := s.GenerateAllocationNo(ctx, constants.AssetAllocationTypeRecall)
|
||||
assert.True(t, len(no) > 0)
|
||||
assert.Equal(t, "RC", no[:2])
|
||||
})
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCarrierStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CMCC_TEST_001",
|
||||
CarrierName: "中国移动测试",
|
||||
CarrierType: constants.CarrierTypeCMCC,
|
||||
Description: "测试运营商",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, carrier)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, carrier.ID)
|
||||
}
|
||||
|
||||
func TestCarrierStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CUCC_TEST_001",
|
||||
CarrierName: "中国联通测试",
|
||||
CarrierType: constants.CarrierTypeCUCC,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
t.Run("查询存在的运营商", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, carrier.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, carrier.CarrierCode, result.CarrierCode)
|
||||
assert.Equal(t, carrier.CarrierName, result.CarrierName)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的运营商", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCarrierStore_GetByCode(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CTCC_TEST_001",
|
||||
CarrierName: "中国电信测试",
|
||||
CarrierType: constants.CarrierTypeCTCC,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
t.Run("查询存在的编码", func(t *testing.T) {
|
||||
result, err := s.GetByCode(ctx, "CTCC_TEST_001")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, carrier.ID, result.ID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的编码", func(t *testing.T) {
|
||||
_, err := s.GetByCode(ctx, "NOT_EXISTS")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCarrierStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CBN_TEST_001",
|
||||
CarrierName: "中国广电测试",
|
||||
CarrierType: constants.CarrierTypeCBN,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
carrier.CarrierName = "中国广电测试-更新"
|
||||
carrier.Description = "更新后的描述"
|
||||
err := s.Update(ctx, carrier)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, carrier.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "中国广电测试-更新", updated.CarrierName)
|
||||
assert.Equal(t, "更新后的描述", updated.Description)
|
||||
}
|
||||
|
||||
func TestCarrierStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "DEL_TEST_001",
|
||||
CarrierName: "待删除运营商",
|
||||
CarrierType: constants.CarrierTypeCMCC,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
err := s.Delete(ctx, carrier.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, carrier.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCarrierStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carriers := []*model.Carrier{
|
||||
{CarrierCode: "LIST_001", CarrierName: "移动1", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled},
|
||||
{CarrierCode: "LIST_002", CarrierName: "联通1", CarrierType: constants.CarrierTypeCUCC, Status: constants.StatusEnabled},
|
||||
{CarrierCode: "LIST_003", CarrierName: "电信1", CarrierType: constants.CarrierTypeCTCC, Status: constants.StatusEnabled},
|
||||
}
|
||||
for _, c := range carriers {
|
||||
require.NoError(t, s.Create(ctx, c))
|
||||
}
|
||||
// 显式更新第三个 carrier 为禁用状态(GORM 不会写入零值)
|
||||
carriers[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, carriers[2]))
|
||||
|
||||
t.Run("查询所有运营商", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按类型过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"carrier_type": constants.CarrierTypeCMCC}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, c := range result {
|
||||
assert.Equal(t, constants.CarrierTypeCMCC, c.CarrierType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按名称模糊搜索", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"carrier_name": "联通"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, c := range result {
|
||||
assert.Contains(t, c.CarrierName, "联通")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-禁用", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusDisabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, c := range result {
|
||||
assert.Equal(t, constants.StatusDisabled, c.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-启用", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusEnabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, c := range result {
|
||||
assert.Equal(t, constants.StatusEnabled, c.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
|
||||
t.Run("默认分页选项", func(t *testing.T) {
|
||||
result, _, err := s.List(ctx, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDeviceSimBindingStore_Create_DuplicateCard(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
bindingStore := NewDeviceSimBindingStore(tx, rdb)
|
||||
deviceStore := NewDeviceStore(tx, rdb)
|
||||
cardStore := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
device1 := &model.Device{DeviceNo: "TEST-DEV-UC-001", Status: 1, MaxSimSlots: 4}
|
||||
device2 := &model.Device{DeviceNo: "TEST-DEV-UC-002", Status: 1, MaxSimSlots: 4}
|
||||
require.NoError(t, deviceStore.Create(ctx, device1))
|
||||
require.NoError(t, deviceStore.Create(ctx, device2))
|
||||
|
||||
card := &model.IotCard{ICCID: "89860012345678910001", CarrierID: 1, Status: 1}
|
||||
require.NoError(t, cardStore.Create(ctx, card))
|
||||
|
||||
now := time.Now()
|
||||
binding1 := &model.DeviceSimBinding{
|
||||
DeviceID: device1.ID,
|
||||
IotCardID: card.ID,
|
||||
SlotPosition: 1,
|
||||
BindStatus: 1,
|
||||
BindTime: &now,
|
||||
}
|
||||
require.NoError(t, bindingStore.Create(ctx, binding1))
|
||||
|
||||
binding2 := &model.DeviceSimBinding{
|
||||
DeviceID: device2.ID,
|
||||
IotCardID: card.ID,
|
||||
SlotPosition: 1,
|
||||
BindStatus: 1,
|
||||
BindTime: &now,
|
||||
}
|
||||
err := bindingStore.Create(ctx, binding2)
|
||||
require.Error(t, err)
|
||||
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok, "错误应该是 AppError 类型")
|
||||
assert.Equal(t, errors.CodeIotCardBoundToDevice, appErr.Code)
|
||||
assert.Contains(t, appErr.Message, "该卡已绑定到其他设备")
|
||||
}
|
||||
|
||||
func TestDeviceSimBindingStore_Create_DuplicateSlot(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
bindingStore := NewDeviceSimBindingStore(tx, rdb)
|
||||
deviceStore := NewDeviceStore(tx, rdb)
|
||||
cardStore := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
device := &model.Device{DeviceNo: "TEST-DEV-UC-003", Status: 1, MaxSimSlots: 4}
|
||||
require.NoError(t, deviceStore.Create(ctx, device))
|
||||
|
||||
card1 := &model.IotCard{ICCID: "89860012345678910011", CarrierID: 1, Status: 1}
|
||||
card2 := &model.IotCard{ICCID: "89860012345678910012", CarrierID: 1, Status: 1}
|
||||
require.NoError(t, cardStore.Create(ctx, card1))
|
||||
require.NoError(t, cardStore.Create(ctx, card2))
|
||||
|
||||
now := time.Now()
|
||||
binding1 := &model.DeviceSimBinding{
|
||||
DeviceID: device.ID,
|
||||
IotCardID: card1.ID,
|
||||
SlotPosition: 1,
|
||||
BindStatus: 1,
|
||||
BindTime: &now,
|
||||
}
|
||||
require.NoError(t, bindingStore.Create(ctx, binding1))
|
||||
|
||||
binding2 := &model.DeviceSimBinding{
|
||||
DeviceID: device.ID,
|
||||
IotCardID: card2.ID,
|
||||
SlotPosition: 1,
|
||||
BindStatus: 1,
|
||||
BindTime: &now,
|
||||
}
|
||||
err := bindingStore.Create(ctx, binding2)
|
||||
require.Error(t, err)
|
||||
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok, "错误应该是 AppError 类型")
|
||||
assert.Equal(t, errors.CodeConflict, appErr.Code)
|
||||
assert.Contains(t, appErr.Message, "该插槽已有绑定的卡")
|
||||
}
|
||||
|
||||
func TestDeviceSimBindingStore_Create_DifferentSlots(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
bindingStore := NewDeviceSimBindingStore(tx, rdb)
|
||||
deviceStore := NewDeviceStore(tx, rdb)
|
||||
cardStore := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
device := &model.Device{DeviceNo: "TEST-DEV-UC-004", Status: 1, MaxSimSlots: 4}
|
||||
require.NoError(t, deviceStore.Create(ctx, device))
|
||||
|
||||
card1 := &model.IotCard{ICCID: "89860012345678910021", CarrierID: 1, Status: 1}
|
||||
card2 := &model.IotCard{ICCID: "89860012345678910022", CarrierID: 1, Status: 1}
|
||||
require.NoError(t, cardStore.Create(ctx, card1))
|
||||
require.NoError(t, cardStore.Create(ctx, card2))
|
||||
|
||||
now := time.Now()
|
||||
binding1 := &model.DeviceSimBinding{
|
||||
DeviceID: device.ID,
|
||||
IotCardID: card1.ID,
|
||||
SlotPosition: 1,
|
||||
BindStatus: 1,
|
||||
BindTime: &now,
|
||||
}
|
||||
require.NoError(t, bindingStore.Create(ctx, binding1))
|
||||
assert.NotZero(t, binding1.ID)
|
||||
|
||||
binding2 := &model.DeviceSimBinding{
|
||||
DeviceID: device.ID,
|
||||
IotCardID: card2.ID,
|
||||
SlotPosition: 2,
|
||||
BindStatus: 1,
|
||||
BindTime: &now,
|
||||
}
|
||||
err := bindingStore.Create(ctx, binding2)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, binding2.ID)
|
||||
}
|
||||
|
||||
func TestDeviceSimBindingStore_ConcurrentBinding(t *testing.T) {
|
||||
db := testutils.GetTestDB(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
deviceStore := NewDeviceStore(db, rdb)
|
||||
cardStore := NewIotCardStore(db, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
device1 := &model.Device{DeviceNo: "TEST-CONCURRENT-001", Status: 1, MaxSimSlots: 4}
|
||||
device2 := &model.Device{DeviceNo: "TEST-CONCURRENT-002", Status: 1, MaxSimSlots: 4}
|
||||
require.NoError(t, deviceStore.Create(ctx, device1))
|
||||
require.NoError(t, deviceStore.Create(ctx, device2))
|
||||
|
||||
card := &model.IotCard{ICCID: "89860012345678920001", CarrierID: 1, Status: 1}
|
||||
require.NoError(t, cardStore.Create(ctx, card))
|
||||
|
||||
t.Cleanup(func() {
|
||||
db.Where("device_id IN ?", []uint{device1.ID, device2.ID}).Delete(&model.DeviceSimBinding{})
|
||||
db.Delete(device1)
|
||||
db.Delete(device2)
|
||||
db.Delete(card)
|
||||
})
|
||||
|
||||
t.Run("并发绑定同一张卡到不同设备", func(t *testing.T) {
|
||||
bindingStore := NewDeviceSimBindingStore(db, rdb)
|
||||
var wg sync.WaitGroup
|
||||
results := make(chan error, 2)
|
||||
|
||||
for i, deviceID := range []uint{device1.ID, device2.ID} {
|
||||
wg.Add(1)
|
||||
go func(devID uint, slot int) {
|
||||
defer wg.Done()
|
||||
now := time.Now()
|
||||
binding := &model.DeviceSimBinding{
|
||||
DeviceID: devID,
|
||||
IotCardID: card.ID,
|
||||
SlotPosition: slot,
|
||||
BindStatus: 1,
|
||||
BindTime: &now,
|
||||
}
|
||||
results <- bindingStore.Create(ctx, binding)
|
||||
}(deviceID, i+1)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(results)
|
||||
|
||||
var successCount, errorCount int
|
||||
for err := range results {
|
||||
if err == nil {
|
||||
successCount++
|
||||
} else {
|
||||
errorCount++
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
if ok {
|
||||
assert.Equal(t, errors.CodeIotCardBoundToDevice, appErr.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, successCount, "应该只有一个请求成功")
|
||||
assert.Equal(t, 1, errorCount, "应该有一个请求失败")
|
||||
})
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uniqueDeviceNoPrefix() string {
|
||||
return fmt.Sprintf("D%d", time.Now().UnixNano()%1000000000)
|
||||
}
|
||||
|
||||
func TestDeviceStore_BatchUpdateSeriesID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewDeviceStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceNoPrefix()
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "001", DeviceName: "测试设备1", Status: 1},
|
||||
{DeviceNo: prefix + "002", DeviceName: "测试设备2", Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, devices))
|
||||
|
||||
t.Run("设置系列ID", func(t *testing.T) {
|
||||
seriesID := uint(100)
|
||||
deviceIDs := []uint{devices[0].ID, devices[1].ID}
|
||||
|
||||
err := s.BatchUpdateSeriesID(ctx, deviceIDs, &seriesID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updatedDevices []*model.Device
|
||||
require.NoError(t, tx.Where("id IN ?", deviceIDs).Find(&updatedDevices).Error)
|
||||
for _, device := range updatedDevices {
|
||||
require.NotNil(t, device.SeriesID)
|
||||
assert.Equal(t, seriesID, *device.SeriesID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("清除系列ID", func(t *testing.T) {
|
||||
deviceIDs := []uint{devices[0].ID}
|
||||
|
||||
err := s.BatchUpdateSeriesID(ctx, deviceIDs, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updatedDevice model.Device
|
||||
require.NoError(t, tx.First(&updatedDevice, devices[0].ID).Error)
|
||||
assert.Nil(t, updatedDevice.SeriesID)
|
||||
})
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := s.BatchUpdateSeriesID(ctx, []uint{}, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeviceStore_ListBySeriesID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewDeviceStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceNoPrefix()
|
||||
seriesID := uint(200)
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "001", DeviceName: "测试设备1", Status: 1, SeriesID: &seriesID},
|
||||
{DeviceNo: prefix + "002", DeviceName: "测试设备2", Status: 1, SeriesID: &seriesID},
|
||||
{DeviceNo: prefix + "003", DeviceName: "测试设备3", Status: 1, SeriesID: nil},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, devices))
|
||||
|
||||
result, err := s.ListBySeriesID(ctx, seriesID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
for _, device := range result {
|
||||
assert.Equal(t, seriesID, *device.SeriesID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeviceStore_List_SeriesIDFilter(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewDeviceStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceNoPrefix()
|
||||
seriesID := uint(300)
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "001", DeviceName: "测试设备1", Status: 1, SeriesID: &seriesID},
|
||||
{DeviceNo: prefix + "002", DeviceName: "测试设备2", Status: 1, SeriesID: &seriesID},
|
||||
{DeviceNo: prefix + "003", DeviceName: "测试设备3", Status: 1, SeriesID: nil},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, devices))
|
||||
|
||||
filters := map[string]interface{}{
|
||||
"series_id": seriesID,
|
||||
"device_no": prefix,
|
||||
}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
assert.Len(t, result, 2)
|
||||
for _, device := range result {
|
||||
assert.Equal(t, seriesID, *device.SeriesID)
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uniqueCardAuthTestPrefix() string {
|
||||
return fmt.Sprintf("ECA%d", time.Now().UnixNano()%1000000000)
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_RevokeByDeviceAuthID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0003", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
deviceAuthID := uint(12345)
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, DeviceAuthID: &deviceAuthID},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, DeviceAuthID: &deviceAuthID},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[2].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, DeviceAuthID: nil},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("成功撤销指定设备授权ID关联的卡授权", func(t *testing.T) {
|
||||
revokerID := uint(2)
|
||||
err := store.RevokeByDeviceAuthID(ctx, deviceAuthID, revokerID)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.ListByEnterprise(ctx, enterprise.ID, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, cards[2].ID, result[0].CardID)
|
||||
|
||||
revokedResult, err := store.ListByEnterprise(ctx, enterprise.ID, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, revokedResult, 3)
|
||||
|
||||
for _, auth := range revokedResult {
|
||||
if auth.DeviceAuthID != nil && *auth.DeviceAuthID == deviceAuthID {
|
||||
assert.NotNil(t, auth.RevokedAt)
|
||||
assert.NotNil(t, auth.RevokedBy)
|
||||
assert.Equal(t, revokerID, *auth.RevokedBy)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("设备授权ID不存在时不报错", func(t *testing.T) {
|
||||
err := store.RevokeByDeviceAuthID(ctx, 99999, uint(1))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
card := &model.IotCard{
|
||||
ICCID: prefix + "0001",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(card).Error)
|
||||
|
||||
t.Run("成功创建卡授权记录", func(t *testing.T) {
|
||||
auth := &model.EnterpriseCardAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
CardID: card.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
Remark: "测试授权",
|
||||
}
|
||||
|
||||
err := store.Create(ctx, auth)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, auth.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
t.Run("成功批量创建卡授权记录", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
|
||||
err := store.BatchCreate(ctx, auths)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, auth := range auths {
|
||||
assert.NotZero(t, auth.ID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := store.BatchCreate(ctx, []*model.EnterpriseCardAuthorization{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_ListByEnterprise(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUintCA(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取未撤销的授权记录", func(t *testing.T) {
|
||||
result, err := store.ListByEnterprise(ctx, enterprise.ID, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, cards[0].ID, result[0].CardID)
|
||||
})
|
||||
|
||||
t.Run("获取所有授权记录包括已撤销", func(t *testing.T) {
|
||||
result, err := store.ListByEnterprise(ctx, enterprise.ID, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func ptrUintCA(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_GetActiveAuthsByCardIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0003", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUintCA(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取有效授权的卡ID映射", func(t *testing.T) {
|
||||
cardIDs := []uint{cards[0].ID, cards[1].ID, cards[2].ID}
|
||||
result, err := store.GetActiveAuthsByCardIDs(ctx, enterprise.ID, cardIDs)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, result[cards[0].ID])
|
||||
assert.False(t, result[cards[1].ID])
|
||||
assert.False(t, result[cards[2].ID])
|
||||
})
|
||||
|
||||
t.Run("空卡ID列表返回空映射", func(t *testing.T) {
|
||||
result, err := store.GetActiveAuthsByCardIDs(ctx, enterprise.ID, []uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
@@ -1,517 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uniqueDeviceAuthTestPrefix() string {
|
||||
return fmt.Sprintf("EDA%d", time.Now().UnixNano()%1000000000)
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
device := &model.Device{
|
||||
DeviceNo: prefix + "_001",
|
||||
DeviceName: "测试设备1",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device).Error)
|
||||
|
||||
t.Run("成功创建授权记录", func(t *testing.T) {
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
Remark: "测试授权",
|
||||
}
|
||||
|
||||
err := store.Create(ctx, auth)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, auth.ID)
|
||||
assert.Equal(t, enterprise.ID, auth.EnterpriseID)
|
||||
assert.Equal(t, device.ID, auth.DeviceID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
{DeviceNo: prefix + "_003", DeviceName: "测试设备3", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
t.Run("成功批量创建授权记录", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[2].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
|
||||
err := store.BatchCreate(ctx, auths)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, auth := range auths {
|
||||
assert.NotZero(t, auth.ID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := store.BatchCreate(ctx, []*model.EnterpriseDeviceAuthorization{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
device := &model.Device{
|
||||
DeviceNo: prefix + "_001",
|
||||
DeviceName: "测试设备1",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device).Error)
|
||||
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
Remark: "测试备注",
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
|
||||
t.Run("成功获取授权记录", func(t *testing.T) {
|
||||
result, err := store.GetByID(ctx, auth.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, auth.ID, result.ID)
|
||||
assert.Equal(t, enterprise.ID, result.EnterpriseID)
|
||||
assert.Equal(t, device.ID, result.DeviceID)
|
||||
assert.Equal(t, "测试备注", result.Remark)
|
||||
})
|
||||
|
||||
t.Run("记录不存在返回错误", func(t *testing.T) {
|
||||
_, err := store.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetByDeviceID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
device := &model.Device{
|
||||
DeviceNo: prefix + "_001",
|
||||
DeviceName: "测试设备1",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device).Error)
|
||||
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
|
||||
t.Run("成功通过设备ID获取授权记录", func(t *testing.T) {
|
||||
result, err := store.GetByDeviceID(ctx, device.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, auth.ID, result.ID)
|
||||
assert.Equal(t, enterprise.ID, result.EnterpriseID)
|
||||
})
|
||||
|
||||
t.Run("设备未授权返回错误", func(t *testing.T) {
|
||||
_, err := store.GetByDeviceID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("已撤销的授权不返回", func(t *testing.T) {
|
||||
device2 := &model.Device{
|
||||
DeviceNo: prefix + "_002",
|
||||
DeviceName: "测试设备2",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device2).Error)
|
||||
|
||||
now := time.Now()
|
||||
revokedAuth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device2.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: now,
|
||||
AuthorizerType: 2,
|
||||
RevokedBy: ptrUint(1),
|
||||
RevokedAt: &now,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, revokedAuth))
|
||||
|
||||
_, err := store.GetByDeviceID(ctx, device2.ID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetByEnterpriseID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUint(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取未撤销的授权记录", func(t *testing.T) {
|
||||
result, err := store.GetByEnterpriseID(ctx, enterprise.ID, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, devices[0].ID, result[0].DeviceID)
|
||||
})
|
||||
|
||||
t.Run("获取所有授权记录包括已撤销", func(t *testing.T) {
|
||||
result, err := store.GetByEnterpriseID(ctx, enterprise.ID, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_ListByEnterprise(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := make([]*model.Device, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
devices[i] = &model.Device{
|
||||
DeviceNo: fmt.Sprintf("%s_%03d", prefix, i+1),
|
||||
DeviceName: fmt.Sprintf("测试设备%d", i+1),
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(devices[i]).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for i, d := range devices {
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: d.ID,
|
||||
AuthorizedBy: uint(i + 1),
|
||||
AuthorizedAt: now.Add(time.Duration(i) * time.Minute),
|
||||
AuthorizerType: 2,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
opts := DeviceAuthListOptions{
|
||||
EnterpriseID: &enterprise.ID,
|
||||
Page: 1,
|
||||
PageSize: 2,
|
||||
}
|
||||
|
||||
result, total, err := store.ListByEnterprise(ctx, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(5), total)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
|
||||
t.Run("按授权人过滤", func(t *testing.T) {
|
||||
authorizerID := uint(1)
|
||||
opts := DeviceAuthListOptions{
|
||||
EnterpriseID: &enterprise.ID,
|
||||
AuthorizerID: &authorizerID,
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
|
||||
result, total, err := store.ListByEnterprise(ctx, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Len(t, result, 1)
|
||||
})
|
||||
|
||||
t.Run("按设备ID过滤", func(t *testing.T) {
|
||||
opts := DeviceAuthListOptions{
|
||||
EnterpriseID: &enterprise.ID,
|
||||
DeviceIDs: []uint{devices[0].ID, devices[1].ID},
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
|
||||
result, total, err := store.ListByEnterprise(ctx, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_RevokeByIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("成功撤销授权", func(t *testing.T) {
|
||||
revokerID := uint(2)
|
||||
err := store.RevokeByIDs(ctx, []uint{auths[0].ID}, revokerID)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.GetByID(ctx, auths[0].ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result.RevokedAt)
|
||||
assert.NotNil(t, result.RevokedBy)
|
||||
assert.Equal(t, revokerID, *result.RevokedBy)
|
||||
})
|
||||
|
||||
t.Run("已撤销的记录不再被重复撤销", func(t *testing.T) {
|
||||
err := store.RevokeByIDs(ctx, []uint{auths[0].ID}, uint(3))
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.GetByID(ctx, auths[0].ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint(2), *result.RevokedBy)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetActiveAuthsByDeviceIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
{DeviceNo: prefix + "_003", DeviceName: "测试设备3", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUint(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取有效授权的设备ID映射", func(t *testing.T) {
|
||||
deviceIDs := []uint{devices[0].ID, devices[1].ID, devices[2].ID}
|
||||
result, err := store.GetActiveAuthsByDeviceIDs(ctx, enterprise.ID, deviceIDs)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, result[devices[0].ID])
|
||||
assert.False(t, result[devices[1].ID])
|
||||
assert.False(t, result[devices[2].ID])
|
||||
})
|
||||
|
||||
t.Run("空设备ID列表返回空映射", func(t *testing.T) {
|
||||
result, err := store.GetActiveAuthsByDeviceIDs(ctx, enterprise.ID, []uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_ListDeviceIDsByEnterprise(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取企业授权设备ID列表", func(t *testing.T) {
|
||||
result, err := store.ListDeviceIDsByEnterprise(ctx, enterprise.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
assert.Contains(t, result, devices[0].ID)
|
||||
assert.Contains(t, result, devices[1].ID)
|
||||
})
|
||||
|
||||
t.Run("无授权记录返回空列表", func(t *testing.T) {
|
||||
result, err := store.ListDeviceIDsByEnterprise(ctx, 99999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func ptrUint(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
@@ -1,524 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uniqueICCIDPrefix() string {
|
||||
return fmt.Sprintf("T%d", time.Now().UnixNano()%1000000000)
|
||||
}
|
||||
|
||||
func TestIotCardStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
card := &model.IotCard{
|
||||
ICCID: "89860012345678901234",
|
||||
CarrierID: 1,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, card)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, card.ID)
|
||||
}
|
||||
|
||||
func TestIotCardStore_ExistsByICCID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
card := &model.IotCard{
|
||||
ICCID: "89860012345678901111",
|
||||
CarrierID: 1,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, card))
|
||||
|
||||
exists, err := s.ExistsByICCID(ctx, "89860012345678901111")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
|
||||
exists, err = s.ExistsByICCID(ctx, "89860012345678909999")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
func TestIotCardStore_ExistsByICCIDBatch(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678902001", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678902002", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678902003", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
result, err := s.ExistsByICCIDBatch(ctx, []string{
|
||||
"89860012345678902001",
|
||||
"89860012345678902002",
|
||||
"89860012345678909999",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result["89860012345678902001"])
|
||||
assert.True(t, result["89860012345678902002"])
|
||||
assert.False(t, result["89860012345678909999"])
|
||||
|
||||
emptyResult, err := s.ExistsByICCIDBatch(ctx, []string{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, emptyResult)
|
||||
}
|
||||
|
||||
func TestIotCardStore_ListStandalone(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueICCIDPrefix()
|
||||
standaloneCards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CarrierID: 1, Status: 1},
|
||||
{ICCID: prefix + "0002", CarrierID: 1, Status: 1},
|
||||
{ICCID: prefix + "0003", CarrierID: 2, Status: 2},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, standaloneCards))
|
||||
|
||||
boundCard := &model.IotCard{
|
||||
ICCID: prefix + "0004",
|
||||
CarrierID: 1,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, boundCard))
|
||||
|
||||
binding := &model.DeviceSimBinding{
|
||||
DeviceID: 1,
|
||||
IotCardID: boundCard.ID,
|
||||
BindStatus: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(binding).Error)
|
||||
|
||||
t.Run("查询所有单卡", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, cards, 3)
|
||||
|
||||
for _, card := range cards {
|
||||
assert.NotEqual(t, boundCard.ID, card.ID, "已绑定的卡不应出现在单卡列表中")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按运营商ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"carrier_id": uint(1), "iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
for _, card := range cards {
|
||||
assert.Equal(t, uint(1), card.CarrierID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": 2, "iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Len(t, cards, 1)
|
||||
assert.Equal(t, 2, cards[0].Status)
|
||||
})
|
||||
|
||||
t.Run("按ICCID模糊查询", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"iccid": prefix + "0001"}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Contains(t, cards[0].ICCID, prefix+"0001")
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, cards, 2)
|
||||
|
||||
cards2, _, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 2, PageSize: 2}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, cards2, 1)
|
||||
})
|
||||
|
||||
t.Run("默认分页选项", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"iccid": prefix}
|
||||
cards, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, cards, 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueICCIDPrefix()
|
||||
batchPrefix := "B" + prefix
|
||||
msisdnPrefix := "199" + prefix[1:8]
|
||||
shopID := uint(time.Now().UnixNano() % 1000000)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "A001", CarrierID: 1, Status: 1, ShopID: &shopID, BatchNo: batchPrefix + "01", MSISDN: msisdnPrefix + "01"},
|
||||
{ICCID: prefix + "A002", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: batchPrefix + "01", MSISDN: msisdnPrefix + "02"},
|
||||
{ICCID: prefix + "A003", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: batchPrefix + "02", MSISDN: msisdnPrefix + "03"},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("按店铺ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"shop_id": shopID, "iccid": prefix}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, shopID, *result[0].ShopID)
|
||||
})
|
||||
|
||||
t.Run("按批次号过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"batch_no": batchPrefix + "01", "iccid": prefix}
|
||||
_, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
})
|
||||
|
||||
t.Run("按MSISDN模糊查询", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"msisdn": msisdnPrefix + "01"}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Contains(t, result[0].MSISDN, msisdnPrefix+"01")
|
||||
})
|
||||
|
||||
t.Run("已分销过滤-true", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"is_distributed": true, "iccid": prefix}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.NotNil(t, result[0].ShopID)
|
||||
})
|
||||
|
||||
t.Run("已分销过滤-false", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"is_distributed": false, "iccid": prefix}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
for _, card := range result {
|
||||
assert.Nil(t, card.ShopID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ICCID范围查询", func(t *testing.T) {
|
||||
filters := map[string]interface{}{
|
||||
"iccid_start": prefix + "A001",
|
||||
"iccid_end": prefix + "A002",
|
||||
}
|
||||
_, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetByICCIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678905001", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678905002", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678905003", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("查询存在的ICCID", func(t *testing.T) {
|
||||
result, err := s.GetByICCIDs(ctx, []string{"89860012345678905001", "89860012345678905002"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的ICCID", func(t *testing.T) {
|
||||
result, err := s.GetByICCIDs(ctx, []string{"89860012345678909999"})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 0)
|
||||
})
|
||||
|
||||
t.Run("空列表返回nil", func(t *testing.T) {
|
||||
result, err := s.GetByICCIDs(ctx, []string{})
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetStandaloneByICCIDRange(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
shopID := uint(100)
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678906001", CarrierID: 1, Status: 1, ShopID: nil},
|
||||
{ICCID: "89860012345678906002", CarrierID: 1, Status: 1, ShopID: nil},
|
||||
{ICCID: "89860012345678906003", CarrierID: 1, Status: 1, ShopID: &shopID},
|
||||
{ICCID: "89860012345678906004", CarrierID: 1, Status: 1, ShopID: &shopID},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("平台查询未分配的卡", func(t *testing.T) {
|
||||
result, err := s.GetStandaloneByICCIDRange(ctx, "89860012345678906001", "89860012345678906004", nil)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
for _, card := range result {
|
||||
assert.Nil(t, card.ShopID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("店铺查询自己的卡", func(t *testing.T) {
|
||||
result, err := s.GetStandaloneByICCIDRange(ctx, "89860012345678906001", "89860012345678906004", &shopID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
for _, card := range result {
|
||||
assert.Equal(t, shopID, *card.ShopID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetStandaloneByFilters(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
shopID := uint(100)
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678907001", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: "BATCH001"},
|
||||
{ICCID: "89860012345678907002", CarrierID: 2, Status: 1, ShopID: nil, BatchNo: "BATCH002"},
|
||||
{ICCID: "89860012345678907003", CarrierID: 1, Status: 2, ShopID: &shopID, BatchNo: "BATCH001"},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("按运营商过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"carrier_id": uint(1)}
|
||||
result, err := s.GetStandaloneByFilters(ctx, filters, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, uint(1), result[0].CarrierID)
|
||||
})
|
||||
|
||||
t.Run("按批次号过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"batch_no": "BATCH001"}
|
||||
result, err := s.GetStandaloneByFilters(ctx, filters, &shopID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, "BATCH001", result[0].BatchNo)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_BatchUpdateShopIDAndStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678908001", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678908002", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
newShopID := uint(200)
|
||||
cardIDs := []uint{cards[0].ID, cards[1].ID}
|
||||
|
||||
err := s.BatchUpdateShopIDAndStatus(ctx, cardIDs, &newShopID, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updatedCards []*model.IotCard
|
||||
require.NoError(t, tx.Where("id IN ?", cardIDs).Find(&updatedCards).Error)
|
||||
for _, card := range updatedCards {
|
||||
assert.Equal(t, newShopID, *card.ShopID)
|
||||
assert.Equal(t, 2, card.Status)
|
||||
}
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := s.BatchUpdateShopIDAndStatus(ctx, []uint{}, nil, 1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_GetBoundCardIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678909001", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678909002", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678909003", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
binding := &model.DeviceSimBinding{
|
||||
DeviceID: 1,
|
||||
IotCardID: cards[0].ID,
|
||||
BindStatus: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(binding).Error)
|
||||
|
||||
cardIDs := []uint{cards[0].ID, cards[1].ID, cards[2].ID}
|
||||
boundIDs, err := s.GetBoundCardIDs(ctx, cardIDs)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, boundIDs, 1)
|
||||
assert.Contains(t, boundIDs, cards[0].ID)
|
||||
|
||||
t.Run("空列表返回nil", func(t *testing.T) {
|
||||
result, err := s.GetBoundCardIDs(ctx, []uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_BatchUpdateSeriesID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678910001", CarrierID: 1, Status: 1},
|
||||
{ICCID: "89860012345678910002", CarrierID: 1, Status: 1},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
t.Run("设置系列ID", func(t *testing.T) {
|
||||
seriesID := uint(100)
|
||||
cardIDs := []uint{cards[0].ID, cards[1].ID}
|
||||
|
||||
err := s.BatchUpdateSeriesID(ctx, cardIDs, &seriesID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updatedCards []*model.IotCard
|
||||
require.NoError(t, tx.Where("id IN ?", cardIDs).Find(&updatedCards).Error)
|
||||
for _, card := range updatedCards {
|
||||
require.NotNil(t, card.SeriesID)
|
||||
assert.Equal(t, seriesID, *card.SeriesID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("清除系列ID", func(t *testing.T) {
|
||||
cardIDs := []uint{cards[0].ID}
|
||||
|
||||
err := s.BatchUpdateSeriesID(ctx, cardIDs, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updatedCard model.IotCard
|
||||
require.NoError(t, tx.First(&updatedCard, cards[0].ID).Error)
|
||||
assert.Nil(t, updatedCard.SeriesID)
|
||||
})
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := s.BatchUpdateSeriesID(ctx, []uint{}, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCardStore_ListBySeriesID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
seriesID := uint(200)
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: "89860012345678911001", CarrierID: 1, Status: 1, SeriesID: &seriesID},
|
||||
{ICCID: "89860012345678911002", CarrierID: 1, Status: 1, SeriesID: &seriesID},
|
||||
{ICCID: "89860012345678911003", CarrierID: 1, Status: 1, SeriesID: nil},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
result, err := s.ListBySeriesID(ctx, seriesID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
for _, card := range result {
|
||||
assert.Equal(t, seriesID, *card.SeriesID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIotCardStore_ListStandalone_SeriesIDFilter(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
s := NewIotCardStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueICCIDPrefix()
|
||||
seriesID := uint(300)
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "S001", CarrierID: 1, Status: 1, SeriesID: &seriesID},
|
||||
{ICCID: prefix + "S002", CarrierID: 1, Status: 1, SeriesID: &seriesID},
|
||||
{ICCID: prefix + "S003", CarrierID: 1, Status: 1, SeriesID: nil},
|
||||
}
|
||||
require.NoError(t, s.CreateBatch(ctx, cards))
|
||||
|
||||
filters := map[string]interface{}{
|
||||
"series_id": seriesID,
|
||||
"iccid": prefix,
|
||||
}
|
||||
result, total, err := s.ListStandalone(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
assert.Len(t, result, 2)
|
||||
for _, card := range result {
|
||||
assert.Equal(t, seriesID, *card.SeriesID)
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOrderItemStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
orderStore := NewOrderStore(tx, rdb)
|
||||
itemStore := NewOrderItemStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
order := &model.Order{
|
||||
OrderNo: orderStore.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypePersonal,
|
||||
BuyerID: 100,
|
||||
TotalAmount: 15000,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
require.NoError(t, orderStore.Create(ctx, order, nil))
|
||||
|
||||
items := []*model.OrderItem{
|
||||
{OrderID: order.ID, PackageID: 1, PackageName: "套餐A", Quantity: 1, UnitPrice: 5000, Amount: 5000},
|
||||
{OrderID: order.ID, PackageID: 2, PackageName: "套餐B", Quantity: 2, UnitPrice: 5000, Amount: 10000},
|
||||
}
|
||||
|
||||
err := itemStore.BatchCreate(ctx, items)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, item := range items {
|
||||
assert.NotZero(t, item.ID)
|
||||
assert.Equal(t, order.ID, item.OrderID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderItemStore_BatchCreate_Empty(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
itemStore := NewOrderItemStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
err := itemStore.BatchCreate(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = itemStore.BatchCreate(ctx, []*model.OrderItem{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestOrderItemStore_ListByOrderID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
orderStore := NewOrderStore(tx, rdb)
|
||||
itemStore := NewOrderItemStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
order := &model.Order{
|
||||
OrderNo: orderStore.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeDevice,
|
||||
BuyerType: model.BuyerTypeAgent,
|
||||
BuyerID: 200,
|
||||
TotalAmount: 20000,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
items := []*model.OrderItem{
|
||||
{PackageID: 10, PackageName: "设备套餐1", Quantity: 1, UnitPrice: 10000, Amount: 10000},
|
||||
{PackageID: 11, PackageName: "设备套餐2", Quantity: 1, UnitPrice: 10000, Amount: 10000},
|
||||
}
|
||||
require.NoError(t, orderStore.Create(ctx, order, items))
|
||||
|
||||
result, err := itemStore.ListByOrderID(ctx, order.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
|
||||
for _, item := range result {
|
||||
assert.Equal(t, order.ID, item.OrderID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderItemStore_ListByOrderIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
orderStore := NewOrderStore(tx, rdb)
|
||||
itemStore := NewOrderItemStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
order1 := &model.Order{
|
||||
OrderNo: orderStore.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypePersonal,
|
||||
BuyerID: 300,
|
||||
TotalAmount: 5000,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
items1 := []*model.OrderItem{
|
||||
{PackageID: 20, PackageName: "套餐X", Quantity: 1, UnitPrice: 5000, Amount: 5000},
|
||||
}
|
||||
require.NoError(t, orderStore.Create(ctx, order1, items1))
|
||||
|
||||
order2 := &model.Order{
|
||||
OrderNo: orderStore.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypePersonal,
|
||||
BuyerID: 300,
|
||||
TotalAmount: 8000,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
items2 := []*model.OrderItem{
|
||||
{PackageID: 21, PackageName: "套餐Y", Quantity: 1, UnitPrice: 3000, Amount: 3000},
|
||||
{PackageID: 22, PackageName: "套餐Z", Quantity: 1, UnitPrice: 5000, Amount: 5000},
|
||||
}
|
||||
require.NoError(t, orderStore.Create(ctx, order2, items2))
|
||||
|
||||
t.Run("查询多个订单的明细", func(t *testing.T) {
|
||||
result, err := itemStore.ListByOrderIDs(ctx, []uint{order1.ID, order2.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 3)
|
||||
})
|
||||
|
||||
t.Run("空订单ID列表", func(t *testing.T) {
|
||||
result, err := itemStore.ListByOrderIDs(ctx, []uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
|
||||
t.Run("不存在的订单ID", func(t *testing.T) {
|
||||
result, err := itemStore.ListByOrderIDs(ctx, []uint{99999})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 0)
|
||||
})
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOrderStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
cardID := uint(1001)
|
||||
order := &model.Order{
|
||||
OrderNo: s.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypePersonal,
|
||||
BuyerID: 100,
|
||||
IotCardID: &cardID,
|
||||
TotalAmount: 9900,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
items := []*model.OrderItem{
|
||||
{
|
||||
PackageID: 1,
|
||||
PackageName: "测试套餐1",
|
||||
Quantity: 1,
|
||||
UnitPrice: 5000,
|
||||
Amount: 5000,
|
||||
},
|
||||
{
|
||||
PackageID: 2,
|
||||
PackageName: "测试套餐2",
|
||||
Quantity: 1,
|
||||
UnitPrice: 4900,
|
||||
Amount: 4900,
|
||||
},
|
||||
}
|
||||
|
||||
err := s.Create(ctx, order, items)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, order.ID)
|
||||
|
||||
for _, item := range items {
|
||||
assert.NotZero(t, item.ID)
|
||||
assert.Equal(t, order.ID, item.OrderID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
order := &model.Order{
|
||||
OrderNo: s.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypeAgent,
|
||||
BuyerID: 200,
|
||||
TotalAmount: 19900,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, order, nil))
|
||||
|
||||
t.Run("查询存在的订单", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, order.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, order.OrderNo, result.OrderNo)
|
||||
assert.Equal(t, order.BuyerType, result.BuyerType)
|
||||
assert.Equal(t, order.TotalAmount, result.TotalAmount)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的订单", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOrderStore_GetByIDWithItems(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
deviceID := uint(2001)
|
||||
order := &model.Order{
|
||||
OrderNo: s.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeDevice,
|
||||
BuyerType: model.BuyerTypePersonal,
|
||||
BuyerID: 300,
|
||||
DeviceID: &deviceID,
|
||||
TotalAmount: 29900,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
items := []*model.OrderItem{
|
||||
{PackageID: 10, PackageName: "设备套餐A", Quantity: 1, UnitPrice: 15000, Amount: 15000},
|
||||
{PackageID: 11, PackageName: "设备套餐B", Quantity: 1, UnitPrice: 14900, Amount: 14900},
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, order, items))
|
||||
|
||||
resultOrder, resultItems, err := s.GetByIDWithItems(ctx, order.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, order.OrderNo, resultOrder.OrderNo)
|
||||
assert.Len(t, resultItems, 2)
|
||||
}
|
||||
|
||||
func TestOrderStore_GetByOrderNo(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
orderNo := s.GenerateOrderNo()
|
||||
order := &model.Order{
|
||||
OrderNo: orderNo,
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypeAgent,
|
||||
BuyerID: 400,
|
||||
TotalAmount: 5000,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, order, nil))
|
||||
|
||||
t.Run("查询存在的订单号", func(t *testing.T) {
|
||||
result, err := s.GetByOrderNo(ctx, orderNo)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, order.ID, result.ID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的订单号", func(t *testing.T) {
|
||||
_, err := s.GetByOrderNo(ctx, "NOT_EXISTS_ORDER_NO")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOrderStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
order := &model.Order{
|
||||
OrderNo: s.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypePersonal,
|
||||
BuyerID: 500,
|
||||
TotalAmount: 10000,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, order, nil))
|
||||
|
||||
order.PaymentMethod = model.PaymentMethodWallet
|
||||
order.PaymentStatus = model.PaymentStatusPaid
|
||||
now := time.Now()
|
||||
order.PaidAt = &now
|
||||
err := s.Update(ctx, order)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, order.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.PaymentMethodWallet, updated.PaymentMethod)
|
||||
assert.Equal(t, model.PaymentStatusPaid, updated.PaymentStatus)
|
||||
assert.NotNil(t, updated.PaidAt)
|
||||
}
|
||||
|
||||
func TestOrderStore_UpdatePaymentStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
order := &model.Order{
|
||||
OrderNo: s.GenerateOrderNo(),
|
||||
OrderType: model.OrderTypeSingleCard,
|
||||
BuyerType: model.BuyerTypeAgent,
|
||||
BuyerID: 600,
|
||||
TotalAmount: 8000,
|
||||
PaymentStatus: model.PaymentStatusPending,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, order, nil))
|
||||
|
||||
now := time.Now()
|
||||
err := s.UpdatePaymentStatus(ctx, order.ID, model.PaymentStatusPaid, &now)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, order.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.PaymentStatusPaid, updated.PaymentStatus)
|
||||
assert.NotNil(t, updated.PaidAt)
|
||||
}
|
||||
|
||||
func TestOrderStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
orders := []*model.Order{
|
||||
{OrderNo: s.GenerateOrderNo(), OrderType: model.OrderTypeSingleCard, BuyerType: model.BuyerTypePersonal, BuyerID: 700, TotalAmount: 1000, PaymentStatus: model.PaymentStatusPending},
|
||||
{OrderNo: s.GenerateOrderNo(), OrderType: model.OrderTypeDevice, BuyerType: model.BuyerTypeAgent, BuyerID: 701, TotalAmount: 2000, PaymentStatus: model.PaymentStatusPaid},
|
||||
{OrderNo: s.GenerateOrderNo(), OrderType: model.OrderTypeSingleCard, BuyerType: model.BuyerTypeAgent, BuyerID: 701, TotalAmount: 3000, PaymentStatus: model.PaymentStatusCancelled},
|
||||
}
|
||||
for _, o := range orders {
|
||||
require.NoError(t, s.Create(ctx, o, nil))
|
||||
}
|
||||
|
||||
t.Run("查询所有订单", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按支付状态过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"payment_status": model.PaymentStatusPending}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, o := range result {
|
||||
assert.Equal(t, model.PaymentStatusPending, o.PaymentStatus)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按订单类型过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"order_type": model.OrderTypeDevice}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, o := range result {
|
||||
assert.Equal(t, model.OrderTypeDevice, o.OrderType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按买家过滤", func(t *testing.T) {
|
||||
filters := map[string]any{"buyer_type": model.BuyerTypeAgent, "buyer_id": uint(701)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, o := range result {
|
||||
assert.Equal(t, model.BuyerTypeAgent, o.BuyerType)
|
||||
assert.Equal(t, uint(701), o.BuyerID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
|
||||
t.Run("默认分页选项", func(t *testing.T) {
|
||||
result, _, err := s.List(ctx, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOrderStore_GenerateOrderNo(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
s := NewOrderStore(tx, rdb)
|
||||
|
||||
orderNo1 := s.GenerateOrderNo()
|
||||
orderNo2 := s.GenerateOrderNo()
|
||||
|
||||
assert.True(t, len(orderNo1) > 0)
|
||||
assert.True(t, len(orderNo1) <= 30)
|
||||
assert.Contains(t, orderNo1, "ORD")
|
||||
assert.NotEqual(t, orderNo1, orderNo2)
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPackageSeriesStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_001",
|
||||
SeriesName: "测试系列",
|
||||
Description: "测试描述",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, series)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, series.ID)
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_002",
|
||||
SeriesName: "测试系列2",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
t.Run("查询存在的系列", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, series.SeriesCode, result.SeriesCode)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的系列", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_GetByCode(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_003",
|
||||
SeriesName: "测试系列3",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
t.Run("查询存在的编码", func(t *testing.T) {
|
||||
result, err := s.GetByCode(ctx, "SERIES_TEST_003")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, series.ID, result.ID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的编码", func(t *testing.T) {
|
||||
_, err := s.GetByCode(ctx, "NOT_EXISTS")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_TEST_004",
|
||||
SeriesName: "测试系列4",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
series.SeriesName = "测试系列4-更新"
|
||||
series.Description = "更新后的描述"
|
||||
err := s.Update(ctx, series)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "测试系列4-更新", updated.SeriesName)
|
||||
assert.Equal(t, "更新后的描述", updated.Description)
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "SERIES_DEL_001",
|
||||
SeriesName: "待删除系列",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
err := s.Delete(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, series.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
seriesList := []*model.PackageSeries{
|
||||
{SeriesCode: "LIST_S_001", SeriesName: "基础套餐", Status: constants.StatusEnabled},
|
||||
{SeriesCode: "LIST_S_002", SeriesName: "高级套餐", Status: constants.StatusEnabled},
|
||||
{SeriesCode: "LIST_S_003", SeriesName: "企业套餐", Status: constants.StatusEnabled},
|
||||
}
|
||||
for _, series := range seriesList {
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
}
|
||||
seriesList[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, seriesList[2]))
|
||||
|
||||
t.Run("查询所有系列", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按名称模糊搜索", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"series_name": "高级"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, series := range result {
|
||||
assert.Contains(t, series.SeriesName, "高级")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusDisabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, series := range result {
|
||||
assert.Equal(t, constants.StatusDisabled, series.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageSeriesStore_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageSeriesStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
series := &model.PackageSeries{
|
||||
SeriesCode: "STATUS_TEST_001",
|
||||
SeriesName: "状态测试系列",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, series))
|
||||
|
||||
err := s.UpdateStatus(ctx, series.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, series.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusDisabled, updated.Status)
|
||||
}
|
||||
@@ -20,7 +20,12 @@ func NewPackageStore(db *gorm.DB) *PackageStore {
|
||||
}
|
||||
|
||||
func (s *PackageStore) Create(ctx context.Context, pkg *model.Package) error {
|
||||
return s.db.WithContext(ctx).Create(pkg).Error
|
||||
// GORM 对零值字段有特殊处理,先创建然后立即更新 enable_realname_activation 字段确保正确设置
|
||||
if err := s.db.WithContext(ctx).Omit("enable_realname_activation").Create(pkg).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 明确更新 enable_realname_activation 字段(包括零值 false)
|
||||
return s.db.WithContext(ctx).Model(pkg).Update("enable_realname_activation", pkg.EnableRealnameActivation).Error
|
||||
}
|
||||
|
||||
func (s *PackageStore) GetByID(ctx context.Context, id uint) (*model.Package, error) {
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPackageStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_001",
|
||||
PackageName: "测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, pkg)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, pkg.ID)
|
||||
}
|
||||
|
||||
func TestPackageStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_002",
|
||||
PackageName: "测试套餐2",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
|
||||
RealDataMB: 2048,
|
||||
CostPrice: 19900,
|
||||
SuggestedRetailPrice: 25800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
t.Run("查询存在的套餐", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pkg.PackageCode, result.PackageCode)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的套餐", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageStore_GetByCode(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_003",
|
||||
PackageName: "测试套餐3",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
|
||||
RealDataMB: 3072,
|
||||
CostPrice: 29900,
|
||||
SuggestedRetailPrice: 39800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
t.Run("查询存在的编码", func(t *testing.T) {
|
||||
result, err := s.GetByCode(ctx, "PKG_TEST_003")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pkg.ID, result.ID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的编码", func(t *testing.T) {
|
||||
_, err := s.GetByCode(ctx, "NOT_EXISTS")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_004",
|
||||
PackageName: "测试套餐4",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
|
||||
RealDataMB: 4096,
|
||||
CostPrice: 39900,
|
||||
SuggestedRetailPrice: 49800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
pkg.PackageName = "测试套餐4-更新"
|
||||
pkg.CostPrice = 49900
|
||||
err := s.Update(ctx, pkg)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "测试套餐4-更新", updated.PackageName)
|
||||
assert.Equal(t, int64(49900), updated.CostPrice)
|
||||
}
|
||||
|
||||
func TestPackageStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_DEL_001",
|
||||
PackageName: "待删除套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
err := s.Delete(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, pkg.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPackageStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkgList := []*model.Package{
|
||||
{
|
||||
PackageCode: "LIST_P_001",
|
||||
PackageName: "基础套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
},
|
||||
{
|
||||
PackageCode: "LIST_P_002",
|
||||
PackageName: "高级套餐",
|
||||
SeriesID: 2,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 12,
|
||||
|
||||
RealDataMB: 10240,
|
||||
CostPrice: 99900,
|
||||
SuggestedRetailPrice: 129800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
},
|
||||
{
|
||||
PackageCode: "LIST_P_003",
|
||||
PackageName: "企业套餐",
|
||||
SeriesID: 3,
|
||||
PackageType: "addon",
|
||||
DurationMonths: 1,
|
||||
|
||||
VirtualDataMB: 5120,
|
||||
CostPrice: 4900,
|
||||
SuggestedRetailPrice: 6800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 2,
|
||||
},
|
||||
}
|
||||
for _, pkg := range pkgList {
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
}
|
||||
pkgList[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, pkgList[2]))
|
||||
|
||||
t.Run("查询所有套餐", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按名称模糊搜索", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"package_name": "高级"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Contains(t, pkg.PackageName, "高级")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按系列筛选", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"series_id": uint(2)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, uint(2), pkg.SeriesID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusDisabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, constants.StatusDisabled, pkg.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按上架状态过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"shelf_status": 2}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, 2, pkg.ShelfStatus)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按类型过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"package_type": "addon"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, pkg := range result {
|
||||
assert.Equal(t, "addon", pkg.PackageType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPackageStore_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "STATUS_TEST_001",
|
||||
PackageName: "状态测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
err := s.UpdateStatus(ctx, pkg.ID, constants.StatusDisabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusDisabled, updated.Status)
|
||||
}
|
||||
|
||||
func TestPackageStore_UpdateShelfStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewPackageStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "SHELF_TEST_001",
|
||||
PackageName: "上架测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
err := s.UpdateShelfStatus(ctx, pkg.ID, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, updated.ShelfStatus)
|
||||
}
|
||||
83
internal/store/postgres/package_usage_daily_record_store.go
Normal file
83
internal/store/postgres/package_usage_daily_record_store.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type PackageUsageDailyRecordStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewPackageUsageDailyRecordStore(db *gorm.DB, redis *redis.Client) *PackageUsageDailyRecordStore {
|
||||
return &PackageUsageDailyRecordStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateOrUpdate 创建或更新日记录(使用 UPSERT)
|
||||
// 如果同一套餐同一天已有记录,则更新;否则创建新记录
|
||||
func (s *PackageUsageDailyRecordStore) CreateOrUpdate(ctx context.Context, record *model.PackageUsageDailyRecord) error {
|
||||
return s.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{
|
||||
{Name: "package_usage_id"},
|
||||
{Name: "date"},
|
||||
},
|
||||
DoUpdates: clause.AssignmentColumns([]string{
|
||||
"daily_usage_mb",
|
||||
"cumulative_usage_mb",
|
||||
"updated_at",
|
||||
}),
|
||||
}).Create(record).Error
|
||||
}
|
||||
|
||||
// GetByDateRange 按日期范围查询日记录
|
||||
// startDate 和 endDate 都是可选的,如果为 nil 则不限制
|
||||
func (s *PackageUsageDailyRecordStore) GetByDateRange(ctx context.Context, packageUsageID uint, startDate, endDate *time.Time) ([]*model.PackageUsageDailyRecord, error) {
|
||||
var records []*model.PackageUsageDailyRecord
|
||||
query := s.db.WithContext(ctx).
|
||||
Where("package_usage_id = ?", packageUsageID).
|
||||
Order("date ASC")
|
||||
|
||||
if startDate != nil {
|
||||
query = query.Where("date >= ?", *startDate)
|
||||
}
|
||||
if endDate != nil {
|
||||
query = query.Where("date <= ?", *endDate)
|
||||
}
|
||||
|
||||
if err := query.Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// GetByDate 查询指定日期的日记录
|
||||
func (s *PackageUsageDailyRecordStore) GetByDate(ctx context.Context, packageUsageID uint, date time.Time) (*model.PackageUsageDailyRecord, error) {
|
||||
var record model.PackageUsageDailyRecord
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("package_usage_id = ? AND date = ?", packageUsageID, date).
|
||||
First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// GetLatestRecord 获取最近的一条日记录
|
||||
func (s *PackageUsageDailyRecordStore) GetLatestRecord(ctx context.Context, packageUsageID uint) (*model.PackageUsageDailyRecord, error) {
|
||||
var record model.PackageUsageDailyRecord
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("package_usage_id = ?", packageUsageID).
|
||||
Order("date DESC").
|
||||
First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
190
internal/store/postgres/package_usage_store.go
Normal file
190
internal/store/postgres/package_usage_store.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PackageUsageStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewPackageUsageStore(db *gorm.DB, redis *redis.Client) *PackageUsageStore {
|
||||
return &PackageUsageStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建套餐使用记录(支持新字段:priority, master_usage_id, has_independent_expiry, pending_realname_activation, data_reset_cycle)
|
||||
// 注意:Status=0 是合法值(待生效),需要明确指定 status 字段以覆盖数据库默认值 1
|
||||
func (s *PackageUsageStore) Create(ctx context.Context, usage *model.PackageUsage) error {
|
||||
// GORM 对零值字段有特殊处理,先创建然后立即更新 status 字段确保正确设置
|
||||
if err := s.db.WithContext(ctx).Omit("status").Create(usage).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 明确更新 status 字段(包括零值)
|
||||
return s.db.WithContext(ctx).Model(usage).Update("status", usage.Status).Error
|
||||
}
|
||||
|
||||
// GetByID 根据ID查询套餐使用记录
|
||||
func (s *PackageUsageStore) GetByID(ctx context.Context, id uint) (*model.PackageUsage, error) {
|
||||
var usage model.PackageUsage
|
||||
if err := s.db.WithContext(ctx).First(&usage, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &usage, nil
|
||||
}
|
||||
|
||||
// Update 更新套餐使用记录
|
||||
func (s *PackageUsageStore) Update(ctx context.Context, usage *model.PackageUsage) error {
|
||||
return s.db.WithContext(ctx).Save(usage).Error
|
||||
}
|
||||
|
||||
// GetActiveMainPackage 查询载体的生效中主套餐
|
||||
// carrierType: "iot_card" 或 "device"
|
||||
// carrierID: iot_card_id 或 device_id
|
||||
func (s *PackageUsageStore) GetActiveMainPackage(ctx context.Context, carrierType string, carrierID uint) (*model.PackageUsage, error) {
|
||||
var usage model.PackageUsage
|
||||
query := s.db.WithContext(ctx).
|
||||
Where("status = ?", constants.PackageUsageStatusActive).
|
||||
Where("master_usage_id IS NULL"). // 主套餐的 master_usage_id 为 NULL
|
||||
Order("priority ASC, activated_at ASC")
|
||||
|
||||
if carrierType == "iot_card" {
|
||||
query = query.Where("iot_card_id = ?", carrierID)
|
||||
} else if carrierType == "device" {
|
||||
query = query.Where("device_id = ?", carrierID)
|
||||
}
|
||||
|
||||
if err := query.First(&usage).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &usage, nil
|
||||
}
|
||||
|
||||
// GetNextPendingMainPackage 查询下一个待生效主套餐(按 priority ASC 排序)
|
||||
func (s *PackageUsageStore) GetNextPendingMainPackage(ctx context.Context, carrierType string, carrierID uint) (*model.PackageUsage, error) {
|
||||
var usage model.PackageUsage
|
||||
query := s.db.WithContext(ctx).
|
||||
Where("status = ?", constants.PackageUsageStatusPending).
|
||||
Where("master_usage_id IS NULL"). // 主套餐
|
||||
Order("priority ASC, created_at ASC")
|
||||
|
||||
if carrierType == "iot_card" {
|
||||
query = query.Where("iot_card_id = ?", carrierID)
|
||||
} else if carrierType == "device" {
|
||||
query = query.Where("device_id = ?", carrierID)
|
||||
}
|
||||
|
||||
if err := query.First(&usage).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &usage, nil
|
||||
}
|
||||
|
||||
// GetActivePackages 查询生效中的主套餐和加油包(按优先级排序:priority ASC, expires_at ASC, activated_at ASC)
|
||||
func (s *PackageUsageStore) GetActivePackages(ctx context.Context, carrierType string, carrierID uint) ([]*model.PackageUsage, error) {
|
||||
var usages []*model.PackageUsage
|
||||
query := s.db.WithContext(ctx).
|
||||
Where("status = ?", constants.PackageUsageStatusActive).
|
||||
Order("priority ASC, expires_at ASC, activated_at ASC")
|
||||
|
||||
if carrierType == "iot_card" {
|
||||
query = query.Where("iot_card_id = ?", carrierID)
|
||||
} else if carrierType == "device" {
|
||||
query = query.Where("device_id = ?", carrierID)
|
||||
}
|
||||
|
||||
if err := query.Find(&usages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return usages, nil
|
||||
}
|
||||
|
||||
// GetAddonsByMasterID 查询主套餐下的所有加油包
|
||||
func (s *PackageUsageStore) GetAddonsByMasterID(ctx context.Context, masterUsageID uint) ([]*model.PackageUsage, error) {
|
||||
var usages []*model.PackageUsage
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("master_usage_id = ?", masterUsageID).
|
||||
Order("priority ASC").
|
||||
Find(&usages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return usages, nil
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新加油包状态
|
||||
func (s *PackageUsageStore) BatchUpdateStatus(ctx context.Context, ids []uint, status int) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PackageUsage{}).
|
||||
Where("id IN ?", ids).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// UpdateDataUsage 更新套餐流量使用(支持事务)
|
||||
// incrementMB: 增量流量(MB)
|
||||
func (s *PackageUsageStore) UpdateDataUsage(ctx context.Context, id uint, incrementMB int64) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PackageUsage{}).
|
||||
Where("id = ?", id).
|
||||
Update("data_usage_mb", gorm.Expr("data_usage_mb + ?", incrementMB)).Error
|
||||
}
|
||||
|
||||
// GetPackagesForReset 查询需要重置的套餐(WHERE next_reset_at <= NOW)
|
||||
func (s *PackageUsageStore) GetPackagesForReset(ctx context.Context, limit int) ([]*model.PackageUsage, error) {
|
||||
var usages []*model.PackageUsage
|
||||
now := time.Now()
|
||||
query := s.db.WithContext(ctx).
|
||||
Where("next_reset_at IS NOT NULL").
|
||||
Where("next_reset_at <= ?", now).
|
||||
Where("status = ?", constants.PackageUsageStatusActive).
|
||||
Order("next_reset_at ASC")
|
||||
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
|
||||
if err := query.Find(&usages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return usages, nil
|
||||
}
|
||||
|
||||
// ResetDataUsage 重置流量(更新 last_reset_at 和 next_reset_at)
|
||||
func (s *PackageUsageStore) ResetDataUsage(ctx context.Context, id uint, lastResetAt, nextResetAt time.Time) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.PackageUsage{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"data_usage_mb": 0,
|
||||
"last_reset_at": lastResetAt,
|
||||
"next_reset_at": nextResetAt,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// ListByCarrier 查询载体的所有套餐使用记录(包括所有状态)
|
||||
func (s *PackageUsageStore) ListByCarrier(ctx context.Context, carrierType string, carrierID uint) ([]*model.PackageUsage, error) {
|
||||
var usages []*model.PackageUsage
|
||||
query := s.db.WithContext(ctx).Order("priority ASC, created_at DESC")
|
||||
|
||||
if carrierType == "iot_card" {
|
||||
query = query.Where("iot_card_id = ?", carrierID)
|
||||
} else if carrierType == "device" {
|
||||
query = query.Where("device_id = ?", carrierID)
|
||||
}
|
||||
|
||||
if err := query.Find(&usages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return usages, nil
|
||||
}
|
||||
@@ -1,395 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRechargeStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewRechargeStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
recharge := &model.RechargeRecord{
|
||||
UserID: 100,
|
||||
WalletID: 200,
|
||||
RechargeNo: "RCH20260131120000000001",
|
||||
Amount: 10000,
|
||||
PaymentMethod: "wechat",
|
||||
Status: 1, // 待支付
|
||||
}
|
||||
|
||||
err := s.Create(ctx, recharge)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, recharge.ID)
|
||||
assert.NotZero(t, recharge.CreatedAt)
|
||||
assert.NotZero(t, recharge.UpdatedAt)
|
||||
}
|
||||
|
||||
func TestRechargeStore_GetByRechargeNo(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewRechargeStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
rechargeNo := "RCH20260131120000000002"
|
||||
recharge := &model.RechargeRecord{
|
||||
UserID: 101,
|
||||
WalletID: 201,
|
||||
RechargeNo: rechargeNo,
|
||||
Amount: 20000,
|
||||
PaymentMethod: "alipay",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, recharge))
|
||||
|
||||
t.Run("查询存在的充值订单", func(t *testing.T) {
|
||||
result, err := s.GetByRechargeNo(ctx, rechargeNo)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
assert.Equal(t, recharge.ID, result.ID)
|
||||
assert.Equal(t, recharge.UserID, result.UserID)
|
||||
assert.Equal(t, recharge.Amount, result.Amount)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的充值订单返回 nil", func(t *testing.T) {
|
||||
result, err := s.GetByRechargeNo(ctx, "NOT_EXISTS_RECHARGE_NO")
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRechargeStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewRechargeStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
recharge := &model.RechargeRecord{
|
||||
UserID: 102,
|
||||
WalletID: 202,
|
||||
RechargeNo: "RCH20260131120000000003",
|
||||
Amount: 30000,
|
||||
PaymentMethod: "wechat",
|
||||
Status: 2, // 已支付
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, recharge))
|
||||
|
||||
t.Run("查询存在的充值订单", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, recharge.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, recharge.RechargeNo, result.RechargeNo)
|
||||
assert.Equal(t, recharge.Status, result.Status)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的充值订单", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRechargeStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewRechargeStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
// 创建测试数据
|
||||
now := time.Now()
|
||||
yesterday := now.Add(-24 * time.Hour)
|
||||
tomorrow := now.Add(24 * time.Hour)
|
||||
|
||||
recharges := []*model.RechargeRecord{
|
||||
{UserID: 200, WalletID: 300, RechargeNo: "RCH20260131120000000010", Amount: 10000, PaymentMethod: "wechat", Status: 1},
|
||||
{UserID: 200, WalletID: 300, RechargeNo: "RCH20260131120000000011", Amount: 20000, PaymentMethod: "alipay", Status: 2},
|
||||
{UserID: 201, WalletID: 301, RechargeNo: "RCH20260131120000000012", Amount: 30000, PaymentMethod: "wechat", Status: 3},
|
||||
{UserID: 201, WalletID: 302, RechargeNo: "RCH20260131120000000013", Amount: 40000, PaymentMethod: "alipay", Status: 1},
|
||||
}
|
||||
for _, r := range recharges {
|
||||
require.NoError(t, s.Create(ctx, r))
|
||||
}
|
||||
|
||||
t.Run("查询所有充值订单", func(t *testing.T) {
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(4))
|
||||
assert.GreaterOrEqual(t, len(result), 4)
|
||||
})
|
||||
|
||||
t.Run("按用户 ID 筛选", func(t *testing.T) {
|
||||
userID := uint(200)
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
UserID: &userID,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, r := range result {
|
||||
assert.Equal(t, uint(200), r.UserID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按钱包 ID 筛选", func(t *testing.T) {
|
||||
walletID := uint(300)
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
WalletID: &walletID,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, r := range result {
|
||||
assert.Equal(t, uint(300), r.WalletID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态筛选", func(t *testing.T) {
|
||||
status := 1 // 待支付
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
Status: &status,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, r := range result {
|
||||
assert.Equal(t, 1, r.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按时间范围筛选", func(t *testing.T) {
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
StartTime: &yesterday,
|
||||
EndTime: &tomorrow,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(4))
|
||||
for _, r := range result {
|
||||
assert.True(t, r.CreatedAt.After(yesterday) || r.CreatedAt.Equal(yesterday))
|
||||
assert.True(t, r.CreatedAt.Before(tomorrow) || r.CreatedAt.Equal(tomorrow))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("组合筛选条件", func(t *testing.T) {
|
||||
userID := uint(201)
|
||||
status := 1
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
UserID: &userID,
|
||||
Status: &status,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, r := range result {
|
||||
assert.Equal(t, uint(201), r.UserID)
|
||||
assert.Equal(t, 1, r.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 2,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(4))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
|
||||
t.Run("默认分页参数", func(t *testing.T) {
|
||||
params := &ListRechargeParams{
|
||||
Page: 0, // 无效值,应使用默认值 1
|
||||
PageSize: 0, // 无效值,应使用默认值 20
|
||||
}
|
||||
result, _, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("按 ID 降序排列", func(t *testing.T) {
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
}
|
||||
result, _, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, len(result), 2)
|
||||
// 验证降序排列
|
||||
for i := 0; i < len(result)-1; i++ {
|
||||
assert.GreaterOrEqual(t, result[i].ID, result[i+1].ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRechargeStore_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewRechargeStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
recharge := &model.RechargeRecord{
|
||||
UserID: 300,
|
||||
WalletID: 400,
|
||||
RechargeNo: "RCH20260131120000000020",
|
||||
Amount: 50000,
|
||||
PaymentMethod: "wechat",
|
||||
Status: 1, // 待支付
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, recharge))
|
||||
|
||||
t.Run("更新状态为已支付(无乐观锁)", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
err := s.UpdateStatus(ctx, recharge.ID, nil, 2, &now, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, recharge.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, updated.Status)
|
||||
assert.NotNil(t, updated.PaidAt)
|
||||
})
|
||||
|
||||
t.Run("更新状态为已完成(带乐观锁)", func(t *testing.T) {
|
||||
oldStatus := 2
|
||||
now := time.Now()
|
||||
err := s.UpdateStatus(ctx, recharge.ID, &oldStatus, 3, nil, &now)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, recharge.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 3, updated.Status)
|
||||
assert.NotNil(t, updated.CompletedAt)
|
||||
})
|
||||
|
||||
t.Run("乐观锁检查失败", func(t *testing.T) {
|
||||
oldStatus := 1 // 当前状态是 3,不是 1
|
||||
err := s.UpdateStatus(ctx, recharge.ID, &oldStatus, 4, nil, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("更新不存在的充值订单", func(t *testing.T) {
|
||||
err := s.UpdateStatus(ctx, 99999, nil, 2, nil, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRechargeStore_UpdatePaymentInfo(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewRechargeStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
recharge := &model.RechargeRecord{
|
||||
UserID: 400,
|
||||
WalletID: 500,
|
||||
RechargeNo: "RCH20260131120000000030",
|
||||
Amount: 60000,
|
||||
PaymentMethod: "wechat",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, recharge))
|
||||
|
||||
t.Run("更新支付渠道和交易号", func(t *testing.T) {
|
||||
channel := "wechat_jsapi"
|
||||
transactionID := "WX1234567890"
|
||||
err := s.UpdatePaymentInfo(ctx, recharge.ID, &channel, &transactionID)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, recharge.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, updated.PaymentChannel)
|
||||
assert.Equal(t, "wechat_jsapi", *updated.PaymentChannel)
|
||||
require.NotNil(t, updated.PaymentTransactionID)
|
||||
assert.Equal(t, "WX1234567890", *updated.PaymentTransactionID)
|
||||
})
|
||||
|
||||
t.Run("只更新支付渠道", func(t *testing.T) {
|
||||
channel := "alipay_h5"
|
||||
err := s.UpdatePaymentInfo(ctx, recharge.ID, &channel, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, recharge.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, updated.PaymentChannel)
|
||||
assert.Equal(t, "alipay_h5", *updated.PaymentChannel)
|
||||
})
|
||||
|
||||
t.Run("只更新交易号", func(t *testing.T) {
|
||||
transactionID := "ALI9876543210"
|
||||
err := s.UpdatePaymentInfo(ctx, recharge.ID, nil, &transactionID)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, recharge.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, updated.PaymentTransactionID)
|
||||
assert.Equal(t, "ALI9876543210", *updated.PaymentTransactionID)
|
||||
})
|
||||
|
||||
t.Run("不更新任何字段", func(t *testing.T) {
|
||||
err := s.UpdatePaymentInfo(ctx, recharge.ID, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("更新不存在的充值订单", func(t *testing.T) {
|
||||
channel := "test_channel"
|
||||
err := s.UpdatePaymentInfo(ctx, 99999, &channel, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRechargeStore_ConcurrentOperations(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
s := NewRechargeStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
// 创建多个充值订单
|
||||
for i := 0; i < 10; i++ {
|
||||
recharge := &model.RechargeRecord{
|
||||
UserID: uint(500 + i),
|
||||
WalletID: uint(600 + i),
|
||||
RechargeNo: "RCH20260131120000000040" + string(rune('0'+i)),
|
||||
Amount: int64(10000 * (i + 1)),
|
||||
PaymentMethod: "wechat",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, recharge))
|
||||
}
|
||||
|
||||
// 验证查询
|
||||
params := &ListRechargeParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
}
|
||||
result, total, err := s.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(10))
|
||||
assert.GreaterOrEqual(t, len(result), 10)
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestShopPackageAllocationStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopPackageAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 1,
|
||||
PackageID: 1,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, allocation)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, allocation.ID)
|
||||
}
|
||||
|
||||
func TestShopPackageAllocationStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopPackageAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 2,
|
||||
PackageID: 2,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 6000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
t.Run("查询存在的分配", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, allocation.ShopID, result.ShopID)
|
||||
assert.Equal(t, allocation.PackageID, result.PackageID)
|
||||
assert.Equal(t, allocation.CostPrice, result.CostPrice)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的分配", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopPackageAllocationStore_GetByShopAndPackage(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopPackageAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 3,
|
||||
PackageID: 3,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 7000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
t.Run("查询存在的店铺和套餐组合", func(t *testing.T) {
|
||||
result, err := s.GetByShopAndPackage(ctx, 3, 3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, allocation.ID, result.ID)
|
||||
assert.Equal(t, uint(3), result.ShopID)
|
||||
assert.Equal(t, uint(3), result.PackageID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的组合", func(t *testing.T) {
|
||||
_, err := s.GetByShopAndPackage(ctx, 99, 99)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopPackageAllocationStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopPackageAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 4,
|
||||
PackageID: 4,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
allocation.CostPrice = 8000
|
||||
err := s.Update(ctx, allocation)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(8000), updated.CostPrice)
|
||||
}
|
||||
|
||||
func TestShopPackageAllocationStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopPackageAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 5,
|
||||
PackageID: 5,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
err := s.Delete(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, allocation.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestShopPackageAllocationStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopPackageAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocations := []*model.ShopPackageAllocation{
|
||||
{ShopID: 10, PackageID: 10, AllocatorShopID: 0, CostPrice: 5000, Status: constants.StatusEnabled},
|
||||
{ShopID: 11, PackageID: 11, AllocatorShopID: 0, CostPrice: 6000, Status: constants.StatusEnabled},
|
||||
{ShopID: 12, PackageID: 12, AllocatorShopID: 10, CostPrice: 7000, Status: constants.StatusEnabled},
|
||||
}
|
||||
for _, a := range allocations {
|
||||
require.NoError(t, s.Create(ctx, a))
|
||||
}
|
||||
allocations[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, allocations[2]))
|
||||
|
||||
t.Run("查询所有分配", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按店铺ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"shop_id": uint(10)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, uint(10), a.ShopID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按套餐ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"package_id": uint(11)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, uint(11), a.PackageID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-启用状态值为1", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": 1}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, 1, a.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-启用", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusEnabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, constants.StatusEnabled, a.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
|
||||
t.Run("默认分页选项", func(t *testing.T) {
|
||||
result, _, err := s.List(ctx, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopPackageAllocationStore_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopPackageAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 20,
|
||||
PackageID: 20,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
err := s.UpdateStatus(ctx, allocation.ID, constants.StatusDisabled, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusDisabled, updated.Status)
|
||||
assert.Equal(t, uint(1), updated.Updater)
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestShopRoleStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
|
||||
err := store.Create(ctx, sr)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, sr.ID)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
srs := []*model.ShopRole{
|
||||
{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := store.BatchCreate(ctx, srs)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, srs[0].ID)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, sr))
|
||||
|
||||
err := store.Delete(ctx, 1, 5)
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err := store.GetByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_DeleteByShopID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
srs := []*model.ShopRole{
|
||||
{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
{
|
||||
ShopID: 1,
|
||||
RoleID: 6,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
require.NoError(t, store.BatchCreate(ctx, srs))
|
||||
|
||||
err := store.DeleteByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err := store.GetByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
}
|
||||
|
||||
func TestShopRoleStore_GetByShopID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("查询已分配角色", func(t *testing.T) {
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, sr))
|
||||
|
||||
results, err := store.GetByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, uint(1), results[0].ShopID)
|
||||
assert.Equal(t, uint(5), results[0].RoleID)
|
||||
})
|
||||
|
||||
t.Run("查询未分配角色的店铺", func(t *testing.T) {
|
||||
results, err := store.GetByShopID(ctx, 999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopRoleStore_GetRoleIDsByShopID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
store := NewShopRoleStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("查询已分配角色的店铺", func(t *testing.T) {
|
||||
sr := &model.ShopRole{
|
||||
ShopID: 1,
|
||||
RoleID: 5,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, sr))
|
||||
|
||||
roleIDs, err := store.GetRoleIDsByShopID(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []uint{5}, roleIDs)
|
||||
})
|
||||
|
||||
t.Run("查询未分配角色的店铺", func(t *testing.T) {
|
||||
roleIDs, err := store.GetRoleIDsByShopID(ctx, 999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, roleIDs)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user