716 lines
21 KiB
Go
716 lines
21 KiB
Go
package customer_binding
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"testing"
|
||
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
)
|
||
|
||
// ---- 测试工具 ----
|
||
|
||
// bindKey 构建 mock 中使用的查找键
|
||
func bindKey(customerID uint, key string) string {
|
||
return fmt.Sprintf("%d:%s", customerID, key)
|
||
}
|
||
|
||
// ---- mock 实现 ----
|
||
|
||
type mockCardReader struct {
|
||
cards map[uint]*model.IotCard
|
||
}
|
||
|
||
func (m *mockCardReader) GetByID(_ context.Context, id uint) (*model.IotCard, error) {
|
||
if c, ok := m.cards[id]; ok {
|
||
return c, nil
|
||
}
|
||
return nil, gorm.ErrRecordNotFound
|
||
}
|
||
|
||
type mockDeviceReader struct {
|
||
devices map[uint]*model.Device
|
||
}
|
||
|
||
func (m *mockDeviceReader) GetByID(_ context.Context, id uint) (*model.Device, error) {
|
||
if d, ok := m.devices[id]; ok {
|
||
return d, nil
|
||
}
|
||
return nil, gorm.ErrRecordNotFound
|
||
}
|
||
|
||
// mockPCDOps 模拟 tb_personal_customer_device 操作
|
||
type mockPCDOps struct {
|
||
// bindings["customerID:virtualNo"] = true 表示有效(status=1)绑定
|
||
bindings map[string]bool
|
||
created []*model.PersonalCustomerDevice
|
||
counts map[string]int64 // virtualNo → count(用于首绑判断)
|
||
allRecords []*model.PersonalCustomerDevice // GetByDeviceNo 和 UpdateStatus/UpdateVirtualNo 使用
|
||
}
|
||
|
||
func (m *mockPCDOps) ExistsByCustomerAndDevice(_ context.Context, customerID uint, deviceNo string) (bool, error) {
|
||
return m.bindings[bindKey(customerID, deviceNo)], nil
|
||
}
|
||
|
||
func (m *mockPCDOps) Create(_ context.Context, record *model.PersonalCustomerDevice) error {
|
||
m.created = append(m.created, record)
|
||
return nil
|
||
}
|
||
|
||
func (m *mockPCDOps) CountByVirtualNo(_ context.Context, virtualNo string) (int64, error) {
|
||
if m.counts != nil {
|
||
return m.counts[virtualNo], nil
|
||
}
|
||
return 0, nil
|
||
}
|
||
|
||
func (m *mockPCDOps) GetByDeviceNo(_ context.Context, deviceNo string) ([]*model.PersonalCustomerDevice, error) {
|
||
var result []*model.PersonalCustomerDevice
|
||
for _, r := range m.allRecords {
|
||
if r.VirtualNo == deviceNo {
|
||
result = append(result, r)
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (m *mockPCDOps) UpdateStatus(_ context.Context, id uint, status int) error {
|
||
for _, r := range m.allRecords {
|
||
if r.ID == id {
|
||
r.Status = status
|
||
return nil
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (m *mockPCDOps) UpdateVirtualNo(_ context.Context, id uint, newVirtualNo string) error {
|
||
for _, r := range m.allRecords {
|
||
if r.ID == id {
|
||
r.VirtualNo = newVirtualNo
|
||
return nil
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// mockPCIOps 模拟 tb_personal_customer_iccid 操作
|
||
type mockPCIOps struct {
|
||
bindings map[string]bool
|
||
created []*model.PersonalCustomerICCID
|
||
counts map[string]int64 // iccid → count
|
||
allRecords []*model.PersonalCustomerICCID // GetByICCID 和 UpdateStatus 使用
|
||
}
|
||
|
||
func (m *mockPCIOps) ExistsByCustomerAndICCID(_ context.Context, customerID uint, iccid string) (bool, error) {
|
||
return m.bindings[bindKey(customerID, iccid)], nil
|
||
}
|
||
|
||
func (m *mockPCIOps) Create(_ context.Context, record *model.PersonalCustomerICCID) error {
|
||
m.created = append(m.created, record)
|
||
return nil
|
||
}
|
||
|
||
func (m *mockPCIOps) CountByICCID(_ context.Context, iccid string) (int64, error) {
|
||
if m.counts != nil {
|
||
return m.counts[iccid], nil
|
||
}
|
||
return 0, nil
|
||
}
|
||
|
||
func (m *mockPCIOps) GetByICCID(_ context.Context, iccid string) ([]*model.PersonalCustomerICCID, error) {
|
||
var result []*model.PersonalCustomerICCID
|
||
for _, r := range m.allRecords {
|
||
if r.ICCID == iccid {
|
||
result = append(result, r)
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (m *mockPCIOps) UpdateStatus(_ context.Context, id uint, status int) error {
|
||
for _, r := range m.allRecords {
|
||
if r.ID == id {
|
||
r.Status = status
|
||
return nil
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ---- 测试 Service 构造器 ----
|
||
|
||
// soldCalls 记录 markAsSold 调用
|
||
type soldCalls struct {
|
||
items []string
|
||
}
|
||
|
||
func (s *soldCalls) mark(_ context.Context, _ *gorm.DB, assetType string, _ uint) error {
|
||
s.items = append(s.items, assetType)
|
||
return nil
|
||
}
|
||
|
||
func newTestService(cards cardReader, devices deviceReader, pcd pcdOps, pci pciOps) (*Service, *soldCalls) {
|
||
sold := &soldCalls{}
|
||
return &Service{
|
||
cards: cards,
|
||
devices: devices,
|
||
readCard: func(ctx context.Context, _ *gorm.DB, id uint) (*model.IotCard, error) {
|
||
return cards.GetByID(ctx, id)
|
||
},
|
||
readDevice: func(ctx context.Context, _ *gorm.DB, id uint) (*model.Device, error) {
|
||
return devices.GetByID(ctx, id)
|
||
},
|
||
makePCD: func(_ *gorm.DB) pcdOps { return pcd },
|
||
makePCI: func(_ *gorm.DB) pciOps { return pci },
|
||
markAsSold: sold.mark,
|
||
}, sold
|
||
}
|
||
|
||
// ---- OwnsAsset 测试 ----
|
||
|
||
// 验证:有虚拟号卡 + 客户有有效绑定 → true(tracer bullet)
|
||
func TestOwnsAsset_有虚拟号卡_有效绑定_返回true(t *testing.T) {
|
||
card := &model.IotCard{VirtualNo: "VN001"}
|
||
card.ID = 1
|
||
|
||
pcd := &mockPCDOps{
|
||
bindings: map[string]bool{bindKey(10, "VN001"): true},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{1: card}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
owned, err := svc.OwnsAsset(context.Background(), 10, "iot_card", 1)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if !owned {
|
||
t.Fatal("期望返回 true,实际返回 false")
|
||
}
|
||
}
|
||
|
||
// 验证:有虚拟号卡 + status=0 的绑定 → false(修复安全缺口)
|
||
func TestOwnsAsset_有虚拟号卡_禁用绑定_返回false(t *testing.T) {
|
||
card := &model.IotCard{VirtualNo: "VN002"}
|
||
card.ID = 2
|
||
|
||
pcd := &mockPCDOps{
|
||
// status=0 的绑定:在 ExistsByCustomerAndDevice 中会过滤掉(bindings 中不存在)
|
||
bindings: map[string]bool{},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{2: card}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
owned, err := svc.OwnsAsset(context.Background(), 10, "iot_card", 2)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if owned {
|
||
t.Fatal("期望返回 false(status=0),实际返回 true")
|
||
}
|
||
}
|
||
|
||
// 验证:有虚拟号卡 + 无绑定 → false
|
||
func TestOwnsAsset_有虚拟号卡_无绑定_返回false(t *testing.T) {
|
||
card := &model.IotCard{VirtualNo: "VN003"}
|
||
card.ID = 3
|
||
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{3: card}},
|
||
&mockDeviceReader{},
|
||
&mockPCDOps{bindings: map[string]bool{}},
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
owned, err := svc.OwnsAsset(context.Background(), 10, "iot_card", 3)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if owned {
|
||
t.Fatal("期望返回 false(无绑定),实际返回 true")
|
||
}
|
||
}
|
||
|
||
// 验证:assetType="card"(来自 assetService.Resolve)等价于 "iot_card"
|
||
func TestOwnsAsset_assetType_card_等价iot_card(t *testing.T) {
|
||
card := &model.IotCard{VirtualNo: "VN_CARD"}
|
||
card.ID = 9
|
||
|
||
pcd := &mockPCDOps{
|
||
bindings: map[string]bool{bindKey(10, "VN_CARD"): true},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{9: card}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
owned, err := svc.OwnsAsset(context.Background(), 10, "card", 9)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if !owned {
|
||
t.Fatal("期望 card 类型等价 iot_card 返回 true,实际 false")
|
||
}
|
||
}
|
||
|
||
// 验证:设备资产 + 有效绑定 → true
|
||
func TestOwnsAsset_设备_有效绑定_返回true(t *testing.T) {
|
||
device := &model.Device{VirtualNo: "DEV001"}
|
||
device.ID = 5
|
||
|
||
pcd := &mockPCDOps{
|
||
bindings: map[string]bool{bindKey(10, "DEV001"): true},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{},
|
||
&mockDeviceReader{devices: map[uint]*model.Device{5: device}},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
owned, err := svc.OwnsAsset(context.Background(), 10, "device", 5)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if !owned {
|
||
t.Fatal("期望返回 true,实际返回 false")
|
||
}
|
||
}
|
||
|
||
// ---- Bind 测试 ----
|
||
|
||
// 验证:首次绑定有虚拟号卡 → 写入 pcd 记录 + 触发 markAssetAsSold
|
||
func TestBind_有虚拟号卡_首次绑定_创建记录并标记已售(t *testing.T) {
|
||
card := &model.IotCard{VirtualNo: "VN010"}
|
||
card.ID = 10
|
||
|
||
pcd := &mockPCDOps{
|
||
bindings: map[string]bool{},
|
||
counts: map[string]int64{"VN010": 0}, // 首次绑定
|
||
}
|
||
svc, sold := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{10: card}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
err := svc.Bind(context.Background(), nil, 20, "iot_card", 10)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if len(pcd.created) != 1 {
|
||
t.Fatalf("期望创建 1 条 pcd 记录,实际: %d", len(pcd.created))
|
||
}
|
||
if pcd.created[0].VirtualNo != "VN010" {
|
||
t.Errorf("期望 VirtualNo=VN010,实际: %s", pcd.created[0].VirtualNo)
|
||
}
|
||
if pcd.created[0].CustomerID != 20 {
|
||
t.Errorf("期望 CustomerID=20,实际: %d", pcd.created[0].CustomerID)
|
||
}
|
||
if len(sold.items) != 1 || sold.items[0] != "iot_card" {
|
||
t.Errorf("期望触发 markAssetAsSold(iot_card),实际: %v", sold.items)
|
||
}
|
||
}
|
||
|
||
// 验证:重复绑定 → 不创建新记录
|
||
func TestBind_有虚拟号卡_已有绑定_不重复创建(t *testing.T) {
|
||
card := &model.IotCard{VirtualNo: "VN011"}
|
||
card.ID = 11
|
||
|
||
pcd := &mockPCDOps{
|
||
bindings: map[string]bool{bindKey(20, "VN011"): true},
|
||
counts: map[string]int64{"VN011": 1},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{11: card}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
err := svc.Bind(context.Background(), nil, 20, "iot_card", 11)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if len(pcd.created) != 0 {
|
||
t.Fatalf("期望不创建新记录,实际创建了 %d 条", len(pcd.created))
|
||
}
|
||
}
|
||
|
||
// 验证:非首次绑定(已有其他客户绑定)→ 创建记录但不触发 markAssetAsSold
|
||
func TestBind_有虚拟号卡_非首次绑定_创建记录不标记已售(t *testing.T) {
|
||
card := &model.IotCard{VirtualNo: "VN012"}
|
||
card.ID = 12
|
||
|
||
pcd := &mockPCDOps{
|
||
bindings: map[string]bool{},
|
||
counts: map[string]int64{"VN012": 1}, // 已有其他绑定
|
||
}
|
||
svc, sold := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{12: card}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
err := svc.Bind(context.Background(), nil, 30, "iot_card", 12)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if len(pcd.created) != 1 {
|
||
t.Fatalf("期望创建 1 条记录,实际: %d", len(pcd.created))
|
||
}
|
||
if len(sold.items) != 0 {
|
||
t.Errorf("期望不触发 markAssetAsSold,实际触发了: %v", sold.items)
|
||
}
|
||
}
|
||
|
||
// ---- Issue 02: 无虚拟号卡 pci 路径测试 ----
|
||
|
||
// 验证:无虚拟号卡 + 客户有有效 PCI 绑定 → true
|
||
func TestOwnsAsset_无虚拟号卡_有效PCI绑定_返回true(t *testing.T) {
|
||
card := &model.IotCard{ICCID: "89860000000000000001"} // VirtualNo 为空
|
||
card.ID = 20
|
||
|
||
pci := &mockPCIOps{
|
||
bindings: map[string]bool{bindKey(10, "89860000000000000001"): true},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{20: card}},
|
||
&mockDeviceReader{},
|
||
&mockPCDOps{bindings: map[string]bool{}},
|
||
pci,
|
||
)
|
||
|
||
owned, err := svc.OwnsAsset(context.Background(), 10, "iot_card", 20)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if !owned {
|
||
t.Fatal("期望返回 true,实际返回 false")
|
||
}
|
||
}
|
||
|
||
// 验证:无虚拟号卡 + 无 PCI 绑定 → false
|
||
func TestOwnsAsset_无虚拟号卡_无PCI绑定_返回false(t *testing.T) {
|
||
card := &model.IotCard{ICCID: "89860000000000000002"} // VirtualNo 为空
|
||
card.ID = 21
|
||
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{21: card}},
|
||
&mockDeviceReader{},
|
||
&mockPCDOps{bindings: map[string]bool{}},
|
||
&mockPCIOps{bindings: map[string]bool{}},
|
||
)
|
||
|
||
owned, err := svc.OwnsAsset(context.Background(), 10, "iot_card", 21)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if owned {
|
||
t.Fatal("期望返回 false(无 PCI 绑定),实际返回 true")
|
||
}
|
||
}
|
||
|
||
// 验证:无虚拟号卡首次绑定 → 写入 pci 记录 + 触发 markAssetAsSold
|
||
func TestBind_无虚拟号卡_首次绑定_创建PCI记录并标记已售(t *testing.T) {
|
||
card := &model.IotCard{ICCID: "89860000000000000010"} // VirtualNo 为空
|
||
card.ID = 30
|
||
|
||
pci := &mockPCIOps{
|
||
bindings: map[string]bool{},
|
||
counts: map[string]int64{"89860000000000000010": 0}, // 首次绑定
|
||
}
|
||
svc, sold := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{30: card}},
|
||
&mockDeviceReader{},
|
||
&mockPCDOps{bindings: map[string]bool{}},
|
||
pci,
|
||
)
|
||
|
||
err := svc.Bind(context.Background(), nil, 50, "iot_card", 30)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if len(pci.created) != 1 {
|
||
t.Fatalf("期望创建 1 条 pci 记录,实际: %d", len(pci.created))
|
||
}
|
||
if pci.created[0].ICCID != "89860000000000000010" {
|
||
t.Errorf("期望 ICCID=89860000000000000010,实际: %s", pci.created[0].ICCID)
|
||
}
|
||
if pci.created[0].CustomerID != 50 {
|
||
t.Errorf("期望 CustomerID=50,实际: %d", pci.created[0].CustomerID)
|
||
}
|
||
if len(sold.items) != 1 || sold.items[0] != "iot_card" {
|
||
t.Errorf("期望触发 markAssetAsSold(iot_card),实际: %v", sold.items)
|
||
}
|
||
}
|
||
|
||
// 验证:无虚拟号卡已有绑定 → 不重复创建
|
||
func TestBind_无虚拟号卡_已有绑定_不重复创建(t *testing.T) {
|
||
card := &model.IotCard{ICCID: "89860000000000000011"} // VirtualNo 为空
|
||
card.ID = 31
|
||
|
||
pci := &mockPCIOps{
|
||
bindings: map[string]bool{bindKey(50, "89860000000000000011"): true},
|
||
counts: map[string]int64{"89860000000000000011": 1},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{31: card}},
|
||
&mockDeviceReader{},
|
||
&mockPCDOps{bindings: map[string]bool{}},
|
||
pci,
|
||
)
|
||
|
||
err := svc.Bind(context.Background(), nil, 50, "iot_card", 31)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if len(pci.created) != 0 {
|
||
t.Fatalf("期望不创建新记录,实际创建了 %d 条", len(pci.created))
|
||
}
|
||
}
|
||
|
||
// ---- Issue 03: Migrate 迁移测试 ----
|
||
|
||
// newMockPCDRecord 构建一条带 ID 的 pcd 记录(用于迁移测试)
|
||
func newMockPCDRecord(id, customerID uint, virtualNo string, status int) *model.PersonalCustomerDevice {
|
||
r := &model.PersonalCustomerDevice{
|
||
CustomerID: customerID,
|
||
VirtualNo: virtualNo,
|
||
Status: status,
|
||
}
|
||
r.ID = id
|
||
return r
|
||
}
|
||
|
||
// newMockPCIRecord 构建一条带 ID 的 pci 记录(用于迁移测试)
|
||
func newMockPCIRecord(id, customerID uint, iccid string, status int) *model.PersonalCustomerICCID {
|
||
r := &model.PersonalCustomerICCID{
|
||
CustomerID: customerID,
|
||
ICCID: iccid,
|
||
Status: status,
|
||
}
|
||
r.ID = id
|
||
return r
|
||
}
|
||
|
||
// 验证:旧卡有虚拟号 + pcd 有绑定 + 新卡有虚拟号 → pcd.virtual_no 更新为新卡虚拟号
|
||
func TestMigrate_有虚拟号旧卡_有绑定_换有虚拟号新卡_更新VirtualNo(t *testing.T) {
|
||
oldCard := &model.IotCard{VirtualNo: "OLD_VN"}
|
||
oldCard.ID = 100
|
||
newCard := &model.IotCard{VirtualNo: "NEW_VN"}
|
||
newCard.ID = 101
|
||
|
||
existing := newMockPCDRecord(1, 50, "OLD_VN", 1)
|
||
pcd := &mockPCDOps{
|
||
bindings: map[string]bool{},
|
||
allRecords: []*model.PersonalCustomerDevice{existing},
|
||
}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{100: oldCard, 101: newCard}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
&mockPCIOps{},
|
||
)
|
||
|
||
err := svc.Migrate(context.Background(), nil, "iot_card", 100, "iot_card", 101)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if existing.VirtualNo != "NEW_VN" {
|
||
t.Errorf("期望 VirtualNo 更新为 NEW_VN,实际: %s", existing.VirtualNo)
|
||
}
|
||
}
|
||
|
||
// 验证:旧卡有虚拟号 + pcd 有绑定 + 新卡无虚拟号 → 旧 pcd status=0,新 pci 创建
|
||
func TestMigrate_有虚拟号旧卡_有绑定_换无虚拟号新卡_迁移到PCI(t *testing.T) {
|
||
oldCard := &model.IotCard{VirtualNo: "OLD_VN2"}
|
||
oldCard.ID = 110
|
||
newCard := &model.IotCard{ICCID: "89860000000000000099"} // 无虚拟号
|
||
newCard.ID = 111
|
||
|
||
existing := newMockPCDRecord(2, 60, "OLD_VN2", 1)
|
||
pcd := &mockPCDOps{
|
||
allRecords: []*model.PersonalCustomerDevice{existing},
|
||
}
|
||
pci := &mockPCIOps{}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{110: oldCard, 111: newCard}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
pci,
|
||
)
|
||
|
||
err := svc.Migrate(context.Background(), nil, "iot_card", 110, "iot_card", 111)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if existing.Status != 0 {
|
||
t.Errorf("期望旧 pcd 记录 status=0,实际: %d", existing.Status)
|
||
}
|
||
if len(pci.created) != 1 {
|
||
t.Fatalf("期望创建 1 条 pci 记录,实际: %d", len(pci.created))
|
||
}
|
||
if pci.created[0].CustomerID != 60 {
|
||
t.Errorf("期望 pci CustomerID=60,实际: %d", pci.created[0].CustomerID)
|
||
}
|
||
if pci.created[0].ICCID != "89860000000000000099" {
|
||
t.Errorf("期望 pci ICCID=89860000000000000099,实际: %s", pci.created[0].ICCID)
|
||
}
|
||
}
|
||
|
||
// 验证:旧卡有虚拟号 + pcd 无绑定 + 新卡无虚拟号 → 跳过,无写入
|
||
func TestMigrate_有虚拟号旧卡_无绑定_换无虚拟号新卡_跳过(t *testing.T) {
|
||
oldCard := &model.IotCard{VirtualNo: "OLD_VN3"}
|
||
oldCard.ID = 120
|
||
newCard := &model.IotCard{ICCID: "89860000000000000088"} // 无虚拟号
|
||
newCard.ID = 121
|
||
|
||
pcd := &mockPCDOps{allRecords: []*model.PersonalCustomerDevice{}} // 空
|
||
pci := &mockPCIOps{}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{120: oldCard, 121: newCard}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
pci,
|
||
)
|
||
|
||
err := svc.Migrate(context.Background(), nil, "iot_card", 120, "iot_card", 121)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if len(pci.created) != 0 {
|
||
t.Fatalf("期望无写入,实际创建了 %d 条 pci 记录", len(pci.created))
|
||
}
|
||
}
|
||
|
||
// 验证:旧卡无虚拟号 + pci 有绑定 + 新卡有虚拟号 → 旧 pci status=0,新 pcd 创建
|
||
func TestMigrate_无虚拟号旧卡_有绑定_换有虚拟号新卡_迁移到PCD(t *testing.T) {
|
||
oldCard := &model.IotCard{ICCID: "89860000000000000077"} // 无虚拟号
|
||
oldCard.ID = 130
|
||
newCard := &model.IotCard{VirtualNo: "NEW_VN3"}
|
||
newCard.ID = 131
|
||
|
||
existingPCI := newMockPCIRecord(3, 70, "89860000000000000077", 1)
|
||
pci := &mockPCIOps{allRecords: []*model.PersonalCustomerICCID{existingPCI}}
|
||
pcd := &mockPCDOps{}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{130: oldCard, 131: newCard}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
pci,
|
||
)
|
||
|
||
err := svc.Migrate(context.Background(), nil, "iot_card", 130, "iot_card", 131)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if existingPCI.Status != 0 {
|
||
t.Errorf("期望旧 pci 记录 status=0,实际: %d", existingPCI.Status)
|
||
}
|
||
if len(pcd.created) != 1 {
|
||
t.Fatalf("期望创建 1 条 pcd 记录,实际: %d", len(pcd.created))
|
||
}
|
||
if pcd.created[0].CustomerID != 70 {
|
||
t.Errorf("期望 pcd CustomerID=70,实际: %d", pcd.created[0].CustomerID)
|
||
}
|
||
if pcd.created[0].VirtualNo != "NEW_VN3" {
|
||
t.Errorf("期望 pcd VirtualNo=NEW_VN3,实际: %s", pcd.created[0].VirtualNo)
|
||
}
|
||
}
|
||
|
||
// 验证:旧卡无虚拟号 + pci 有绑定 + 新卡也无虚拟号 → 旧 pci status=0,新 pci 创建新 ICCID
|
||
func TestMigrate_无虚拟号旧卡_有绑定_换无虚拟号新卡_迁移PCI(t *testing.T) {
|
||
oldCard := &model.IotCard{ICCID: "89860000000000000066"} // 无虚拟号
|
||
oldCard.ID = 140
|
||
newCard := &model.IotCard{ICCID: "89860000000000000055"} // 无虚拟号
|
||
newCard.ID = 141
|
||
|
||
existingPCI := newMockPCIRecord(4, 80, "89860000000000000066", 1)
|
||
pci := &mockPCIOps{allRecords: []*model.PersonalCustomerICCID{existingPCI}}
|
||
pcd := &mockPCDOps{}
|
||
svc, _ := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{140: oldCard, 141: newCard}},
|
||
&mockDeviceReader{},
|
||
pcd,
|
||
pci,
|
||
)
|
||
|
||
err := svc.Migrate(context.Background(), nil, "iot_card", 140, "iot_card", 141)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if existingPCI.Status != 0 {
|
||
t.Errorf("期望旧 pci 记录 status=0,实际: %d", existingPCI.Status)
|
||
}
|
||
if len(pci.created) != 1 {
|
||
t.Fatalf("期望创建 1 条新 pci 记录,实际: %d", len(pci.created))
|
||
}
|
||
if pci.created[0].CustomerID != 80 {
|
||
t.Errorf("期望 pci CustomerID=80,实际: %d", pci.created[0].CustomerID)
|
||
}
|
||
if pci.created[0].ICCID != "89860000000000000055" {
|
||
t.Errorf("期望 pci ICCID=89860000000000000055,实际: %s", pci.created[0].ICCID)
|
||
}
|
||
}
|
||
|
||
// 验证:无虚拟号卡非首次绑定(已有其他客户)→ 创建记录但不触发 markAssetAsSold
|
||
func TestBind_无虚拟号卡_非首次绑定_创建记录不标记已售(t *testing.T) {
|
||
card := &model.IotCard{ICCID: "89860000000000000012"} // VirtualNo 为空
|
||
card.ID = 32
|
||
|
||
pci := &mockPCIOps{
|
||
bindings: map[string]bool{},
|
||
counts: map[string]int64{"89860000000000000012": 1}, // 已有其他客户绑定
|
||
}
|
||
svc, sold := newTestService(
|
||
&mockCardReader{cards: map[uint]*model.IotCard{32: card}},
|
||
&mockDeviceReader{},
|
||
&mockPCDOps{bindings: map[string]bool{}},
|
||
pci,
|
||
)
|
||
|
||
err := svc.Bind(context.Background(), nil, 60, "iot_card", 32)
|
||
|
||
if err != nil {
|
||
t.Fatalf("期望无错误,实际: %v", err)
|
||
}
|
||
if len(pci.created) != 1 {
|
||
t.Fatalf("期望创建 1 条记录,实际: %d", len(pci.created))
|
||
}
|
||
if len(sold.items) != 0 {
|
||
t.Errorf("期望不触发 markAssetAsSold,实际触发了: %v", sold.items)
|
||
}
|
||
}
|