package handler import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "github.com/wangjia/jiu/backend/internal/model" "github.com/wangjia/jiu/backend/testutil" ) func setupPublicRouter(db *gorm.DB) *gin.Engine { h := NewPublicHandler(db) r := gin.New() r.Use(gin.Recovery()) r.GET("/api/v1/public/shops/:shop_code/products", h.ListShopProducts) return r } // 给商品补 public_id(CreateTestProduct 默认不设) func setPublicID(db *gorm.DB, productID uint64, pub string) { require := func(err error) { if err != nil { panic(err) } } require(db.Model(&model.Product{}).Where("id = ?", productID).Update("public_id", pub).Error) } func addInventory(db *gorm.DB, shopID, warehouseID, productID uint64, qty float64) { wid := warehouseID pid := productID if err := db.Create(&model.Inventory{ ShopID: shopID, WarehouseID: &wid, ProductID: &pid, Quantity: qty, }).Error; err != nil { panic(err) } } func TestPublicHandler_ListShopProducts_InStockOnly(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PUB001") wh := testutil.CreateTestWarehouse(db, shop.ID, "仓") r := setupPublicRouter(db) // A:有库存 5 → 应出现,quantity=5 pa := testutil.CreateTestProduct(db, shop.ID, "茅台A") setPublicID(db, pa.ID, "pub-a") addInventory(db, shop.ID, wh.ID, pa.ID, 5) // B:有 public_id 但无库存 → 不出现 pb := testutil.CreateTestProduct(db, shop.ID, "五粮液B") setPublicID(db, pb.ID, "pub-b") // C:两条库存 3+2 → 出现,quantity=5 pc := testutil.CreateTestProduct(db, shop.ID, "汾酒C") setPublicID(db, pc.ID, "pub-c") addInventory(db, shop.ID, wh.ID, pc.ID, 3) addInventory(db, shop.ID, wh.ID, pc.ID, 2) // D:有库存但无 public_id → 不出现 pd := testutil.CreateTestProduct(db, shop.ID, "无公开D") addInventory(db, shop.ID, wh.ID, pd.ID, 9) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/public/shops/PUB001/products", nil) r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) // 只 A、C 两个有库存 + 有 public_id assert.Equal(t, float64(2), resp["total"]) data := resp["data"].([]interface{}) require.Len(t, data, 2) byName := map[string]map[string]interface{}{} for _, it := range data { m := it.(map[string]interface{}) byName[m["name"].(string)] = m } require.Contains(t, byName, "茅台A") require.Contains(t, byName, "汾酒C") assert.NotContains(t, byName, "五粮液B") // 无库存 assert.NotContains(t, byName, "无公开D") // 无 public_id // 数量正确(C 聚合 3+2=5) assert.Equal(t, float64(5), byName["茅台A"]["quantity"]) assert.Equal(t, float64(5), byName["汾酒C"]["quantity"]) // 带上了序列号 code assert.Equal(t, "P-茅台A", byName["茅台A"]["code"]) } func TestPublicHandler_ListShopProducts_Isolation(t *testing.T) { db := testutil.SetupTestDB() shopA := testutil.CreateTestShop(db, "PUBA") shopB := testutil.CreateTestShop(db, "PUBB") whB := testutil.CreateTestWarehouse(db, shopB.ID, "仓B") r := setupPublicRouter(db) // B 店有个有库存商品 pb := testutil.CreateTestProduct(db, shopB.ID, "他店商品") setPublicID(db, pb.ID, "pub-x") addInventory(db, shopB.ID, whB.ID, pb.ID, 7) // 查 A 店:看不到 B 店商品 w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/public/shops/"+shopA.Code+"/products", nil) r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, float64(0), resp["total"]) }