package handler import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "github.com/wangjia/jiu/backend/testutil" ) // 反爬收紧回归(2026-07):page_size 上限夹到 20;公开接口响应绝不出现 // 成本类敏感字段名(白名单 DTO 防未来 Preload 全量 struct 回归泄露)。 func setupPublicFullRouter(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) r.GET("/api/v1/public/products/:public_id", h.GetProduct) return r } func TestPublicListPageSizeClampedTo20(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PUBPS") wh := testutil.CreateTestWarehouse(db, shop.ID, "仓") r := setupPublicFullRouter(db) for i := 0; i < 25; i++ { p := testutil.CreateTestProduct(db, shop.ID, fmt.Sprintf("酒%02d", i)) setPublicID(db, p.ID, fmt.Sprintf("pub-ps-%02d", i)) addInventory(db, shop.ID, wh.ID, p.ID, 1) } w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/public/shops/PUBPS/products?page_size=50", 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(25), resp["total"]) data := resp["data"].([]interface{}) assert.Len(t, data, 20, "page_size=50 应被夹到上限 20") assert.Equal(t, float64(20), resp["page_size"]) } // 公开响应不得出现的敏感字段名(成本/利润口径只对管理员,公开面零暴露)。 var sensitiveFieldNames = []string{"cost", "purchase_price", "profit"} func assertNoSensitiveFields(t *testing.T, body string) { t.Helper() lower := strings.ToLower(body) for _, f := range sensitiveFieldNames { assert.NotContains(t, lower, f, "公开接口响应不应含敏感字段名 %q", f) } } func TestPublicResponsesExcludeCostFields(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PUBSEC") wh := testutil.CreateTestWarehouse(db, shop.ID, "仓") r := setupPublicFullRouter(db) p := testutil.CreateTestProduct(db, shop.ID, "敏感字段酒") setPublicID(db, p.ID, "pub-sec-1") addInventory(db, shop.ID, wh.ID, p.ID, 3) // 列表 w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/public/shops/PUBSEC/products", nil) r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) assertNoSensitiveFields(t, w.Body.String()) // 单品 w2 := httptest.NewRecorder() req2, _ := http.NewRequest("GET", "/api/v1/public/products/pub-sec-1", nil) r.ServeHTTP(w2, req2) require.Equal(t, http.StatusOK, w2.Code) assertNoSensitiveFields(t, w2.Body.String()) }