Files
junhong_cmp_fiber/internal/service/customer_binding/service_test.go
break ba435dd6a6
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
关于绑定的问题
2026-06-22 12:08:41 +09:00

716 lines
21 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 测试 ----
// 验证:有虚拟号卡 + 客户有有效绑定 → truetracer 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("期望返回 falsestatus=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)
}
}