package integration import ( "context" "encoding/json" "fmt" "testing" "time" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" pkggorm "github.com/break/junhong_cmp_fiber/pkg/gorm" "github.com/break/junhong_cmp_fiber/pkg/response" "github.com/break/junhong_cmp_fiber/tests/testutils/integ" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestStandaloneCardAllocation_AllocateByList(t *testing.T) { env := integ.NewIntegrationTestEnv(t) // 创建测试数据 shop := env.CreateTestShop("测试店铺", 1, nil) subShop := env.CreateTestShop("测试下级店铺", 2, &shop.ID) agentAccount := env.CreateTestAccount("agent_alloc", "password123", constants.UserTypeAgent, &shop.ID, nil) cards := []*model.IotCard{ {ICCID: "ALLOC_TEST001", CardType: "data_card", CarrierID: 1, Status: constants.IotCardStatusInStock}, {ICCID: "ALLOC_TEST002", CardType: "data_card", CarrierID: 1, Status: constants.IotCardStatusInStock}, {ICCID: "ALLOC_TEST003", CardType: "data_card", CarrierID: 1, Status: constants.IotCardStatusInStock}, } for _, card := range cards { require.NoError(t, env.TX.Create(card).Error) } t.Run("平台分配卡给一级店铺", func(t *testing.T) { reqBody := map[string]interface{}{ "to_shop_id": shop.ID, "selection_type": "list", "iccids": []string{"ALLOC_TEST001", "ALLOC_TEST002"}, "remark": "测试分配", } bodyBytes, _ := json.Marshal(reqBody) resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/iot-cards/standalone/allocate", bodyBytes) require.NoError(t, err) defer resp.Body.Close() var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) t.Logf("Allocate response: code=%d, message=%s, data=%v", result.Code, result.Message, result.Data) assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 0, result.Code, "应返回成功: %s", result.Message) if result.Data != nil { dataMap := result.Data.(map[string]interface{}) assert.Equal(t, float64(2), dataMap["total_count"]) assert.Equal(t, float64(2), dataMap["success_count"]) assert.Equal(t, float64(0), dataMap["fail_count"]) } ctx := pkggorm.SkipDataPermission(context.Background()) var updatedCards []model.IotCard env.RawDB().WithContext(ctx).Where("iccid IN ?", []string{"ALLOC_TEST001", "ALLOC_TEST002"}).Find(&updatedCards) for _, card := range updatedCards { assert.Equal(t, shop.ID, *card.ShopID, "卡应分配给目标店铺") assert.Equal(t, constants.IotCardStatusDistributed, card.Status, "状态应为已分销") } var recordCount int64 env.RawDB().WithContext(ctx).Model(&model.AssetAllocationRecord{}). Where("asset_identifier IN ?", []string{"ALLOC_TEST001", "ALLOC_TEST002"}). Count(&recordCount) assert.Equal(t, int64(2), recordCount, "应创建2条分配记录") }) t.Run("代理分配卡给下级店铺", func(t *testing.T) { reqBody := map[string]interface{}{ "to_shop_id": subShop.ID, "selection_type": "list", "iccids": []string{"ALLOC_TEST001"}, "remark": "代理分配测试", } bodyBytes, _ := json.Marshal(reqBody) resp, err := env.AsUser(agentAccount).Request("POST", "/api/admin/iot-cards/standalone/allocate", bodyBytes) require.NoError(t, err) defer resp.Body.Close() var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) t.Logf("Agent allocate response: code=%d, message=%s", result.Code, result.Message) assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 0, result.Code, "代理应能分配给下级: %s", result.Message) }) t.Run("分配不存在的卡应返回空结果", func(t *testing.T) { reqBody := map[string]interface{}{ "to_shop_id": shop.ID, "selection_type": "list", "iccids": []string{"NOT_EXISTS_001", "NOT_EXISTS_002"}, } bodyBytes, _ := json.Marshal(reqBody) resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/iot-cards/standalone/allocate", bodyBytes) require.NoError(t, err) defer resp.Body.Close() var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) if result.Data != nil { dataMap := result.Data.(map[string]interface{}) assert.Equal(t, float64(0), dataMap["total_count"]) } }) } func TestStandaloneCardAllocation_Recall(t *testing.T) { env := integ.NewIntegrationTestEnv(t) // 创建测试数据 shop := env.CreateTestShop("测试店铺", 1, nil) shopID := shop.ID cards := []*model.IotCard{ {ICCID: "ALLOC_TEST101", CardType: "data_card", CarrierID: 1, Status: constants.IotCardStatusDistributed, ShopID: &shopID}, {ICCID: "ALLOC_TEST102", CardType: "data_card", CarrierID: 1, Status: constants.IotCardStatusDistributed, ShopID: &shopID}, } for _, card := range cards { require.NoError(t, env.TX.Create(card).Error) } t.Run("平台回收卡", func(t *testing.T) { reqBody := map[string]interface{}{ "from_shop_id": shop.ID, "selection_type": "list", "iccids": []string{"ALLOC_TEST101"}, "remark": "平台回收测试", } bodyBytes, _ := json.Marshal(reqBody) resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/iot-cards/standalone/recall", bodyBytes) require.NoError(t, err) defer resp.Body.Close() var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) t.Logf("Recall response: code=%d, message=%s, data=%v", result.Code, result.Message, result.Data) assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 0, result.Code, "应返回成功: %s", result.Message) ctx := pkggorm.SkipDataPermission(context.Background()) var recalledCard model.IotCard env.RawDB().WithContext(ctx).Where("iccid = ?", "ALLOC_TEST101").First(&recalledCard) assert.Nil(t, recalledCard.ShopID, "平台回收后shop_id应为NULL") assert.Equal(t, constants.IotCardStatusInStock, recalledCard.Status, "状态应恢复为在库") }) } func TestAssetAllocationRecord_List(t *testing.T) { env := integ.NewIntegrationTestEnv(t) // 创建测试数据 shop := env.CreateTestShop("测试店铺", 1, nil) var superAdminAccount model.Account require.NoError(t, env.RawDB().Where("user_type = ?", constants.UserTypeSuperAdmin).First(&superAdminAccount).Error) fromShopID := shop.ID records := []*model.AssetAllocationRecord{ { AllocationNo: fmt.Sprintf("AL%d001", time.Now().UnixNano()), AllocationType: constants.AssetAllocationTypeAllocate, AssetType: constants.AssetTypeIotCard, AssetID: 1, AssetIdentifier: "ALLOC_TEST_REC001", FromOwnerType: constants.OwnerTypePlatform, ToOwnerType: constants.OwnerTypeShop, ToOwnerID: shop.ID, OperatorID: superAdminAccount.ID, }, { AllocationNo: fmt.Sprintf("RC%d001", time.Now().UnixNano()), AllocationType: constants.AssetAllocationTypeRecall, AssetType: constants.AssetTypeIotCard, AssetID: 2, AssetIdentifier: "ALLOC_TEST_REC002", FromOwnerType: constants.OwnerTypeShop, FromOwnerID: &fromShopID, ToOwnerType: constants.OwnerTypePlatform, ToOwnerID: 0, OperatorID: superAdminAccount.ID, }, } for _, record := range records { require.NoError(t, env.TX.Create(record).Error) } t.Run("获取分配记录列表", func(t *testing.T) { resp, err := env.AsSuperAdmin().Request("GET", "/api/admin/asset-allocation-records?page=1&page_size=20", nil) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, 200, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) }) t.Run("按分配类型过滤", func(t *testing.T) { resp, err := env.AsSuperAdmin().Request("GET", "/api/admin/asset-allocation-records?allocation_type=allocate", nil) require.NoError(t, err) defer resp.Body.Close() var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) }) t.Run("获取分配记录详情", func(t *testing.T) { url := fmt.Sprintf("/api/admin/asset-allocation-records/%d", records[0].ID) resp, err := env.AsSuperAdmin().Request("GET", url, nil) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, 200, resp.StatusCode) var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.Equal(t, 0, result.Code) }) t.Run("未认证请求应返回错误", func(t *testing.T) { resp, err := env.ClearAuth().Request("GET", "/api/admin/asset-allocation-records", nil) require.NoError(t, err) defer resp.Body.Close() var result response.Response err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) assert.NotEqual(t, 0, result.Code, "未认证请求应返回错误码") }) }