package handler import ( "fmt" "net/http" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "gorm.io/gorm" "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" "github.com/wangjia/jiu/backend/testutil" ) // setupOptionRouter 注册 product-options 名称路由(含 JWT)。 func setupOptionRouter(db *gorm.DB) *gin.Engine { h := NewProductOptionHandler(db) r := gin.New() r.Use(gin.Recovery()) api := r.Group("/api/v1") api.Use(middleware.JWT(db)) names := api.Group("/product-options/names") names.GET("", h.ListNames) return r } func TestProductOptionHandler_ListNamesKeyword(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "OPT001") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupOptionRouter(db) db.Create(&model.ProductNameOption{ TenantBase: model.TenantBase{ShopID: shop.ID}, Code: "P001", Name: "茅台", }) db.Create(&model.ProductNameOption{ TenantBase: model.TenantBase{ShopID: shop.ID}, Code: "P002", Name: "五粮液", }) // 1. 无 keyword → 返回全部 w := makeRequest(r, "GET", "/api/v1/product-options/names", token, nil) assert.Equal(t, http.StatusOK, w.Code) data := parseResponse(w)["data"].([]interface{}) assert.Len(t, data, 2) // 2. keyword 命中名称 w = makeRequest(r, "GET", "/api/v1/product-options/names?keyword=五粮", token, nil) assert.Equal(t, http.StatusOK, w.Code) data = parseResponse(w)["data"].([]interface{}) assert.Len(t, data, 1) assert.Equal(t, "五粮液", data[0].(map[string]interface{})["name"]) // 3. keyword 命中编码 w = makeRequest(r, "GET", "/api/v1/product-options/names?keyword=P001", token, nil) assert.Equal(t, http.StatusOK, w.Code) data = parseResponse(w)["data"].([]interface{}) assert.Len(t, data, 1) assert.Equal(t, "茅台", data[0].(map[string]interface{})["name"]) // 4. keyword 无命中 → 空 w = makeRequest(r, "GET", "/api/v1/product-options/names?keyword=不存在", token, nil) assert.Equal(t, http.StatusOK, w.Code) data = parseResponse(w)["data"].([]interface{}) assert.Len(t, data, 0) } func TestProductOptionHandler_ListNamesIsolation(t *testing.T) { db := testutil.SetupTestDB() shopA := testutil.CreateTestShop(db, "OPT_A") userA := testutil.CreateTestUser(db, shopA.ID, "adminA", "pass", "admin") tokenA := getAuthToken(userA.ID, shopA.ID, "admin") shopB := testutil.CreateTestShop(db, "OPT_B") r := setupOptionRouter(db) db.Create(&model.ProductNameOption{ TenantBase: model.TenantBase{ShopID: shopB.ID}, Name: "他店商品", }) // A 店即使 keyword 命中 B 店数据也查不到(shop_id 隔离) w := makeRequest(r, "GET", "/api/v1/product-options/names?keyword=他店", tokenA, nil) assert.Equal(t, http.StatusOK, w.Code) data := parseResponse(w)["data"].([]interface{}) assert.Len(t, data, 0) } // setupCategoryRouter 注册 香型/分类 字典 CRUD 路由(含 JWT)。 func setupCategoryRouter(db *gorm.DB) *gin.Engine { h := NewProductOptionHandler(db) r := gin.New() r.Use(gin.Recovery()) api := r.Group("/api/v1") api.Use(middleware.JWT(db)) cats := api.Group("/product-options/categories") cats.GET("", h.ListCategories) cats.POST("", h.CreateCategory) cats.PUT("/:id", h.UpdateCategory) cats.DELETE("/:id", h.DeleteCategory) return r } func TestProductOptionHandler_CategoryCRUD(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "CAT001") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupCategoryRouter(db) // 1. 创建 w := makeRequest(r, "POST", "/api/v1/product-options/categories", token, map[string]interface{}{"name": "酱香"}) assert.Equal(t, http.StatusCreated, w.Code) id := parseResponse(w)["data"].(map[string]interface{})["id"] // 2. 列表命中 w = makeRequest(r, "GET", "/api/v1/product-options/categories", token, nil) assert.Equal(t, http.StatusOK, w.Code) data := parseResponse(w)["data"].([]interface{}) assert.Len(t, data, 1) assert.Equal(t, "酱香", data[0].(map[string]interface{})["name"]) // 3. 更新 w = makeRequest(r, "PUT", fmt.Sprintf("/api/v1/product-options/categories/%v", id), token, map[string]interface{}{"name": "浓香"}) assert.Equal(t, http.StatusOK, w.Code) w = makeRequest(r, "GET", "/api/v1/product-options/categories?keyword=浓", token, nil) data = parseResponse(w)["data"].([]interface{}) assert.Len(t, data, 1) // 4. 删除 w = makeRequest(r, "DELETE", fmt.Sprintf("/api/v1/product-options/categories/%v", id), token, nil) assert.Equal(t, http.StatusOK, w.Code) w = makeRequest(r, "GET", "/api/v1/product-options/categories", token, nil) assert.Len(t, parseResponse(w)["data"].([]interface{}), 0) } func TestProductOptionHandler_CategoryIsolation(t *testing.T) { db := testutil.SetupTestDB() shopA := testutil.CreateTestShop(db, "CAT_A") userA := testutil.CreateTestUser(db, shopA.ID, "adminA", "pass", "admin") tokenA := getAuthToken(userA.ID, shopA.ID, "admin") shopB := testutil.CreateTestShop(db, "CAT_B") r := setupCategoryRouter(db) db.Create(&model.ProductCategory{ TenantBase: model.TenantBase{ShopID: shopB.ID}, Name: "他店香型", }) // A 店查不到 B 店香型(shop_id 隔离) w := makeRequest(r, "GET", "/api/v1/product-options/categories?keyword=他店", tokenA, nil) assert.Equal(t, http.StatusOK, w.Code) assert.Len(t, parseResponse(w)["data"].([]interface{}), 0) }