290054dc8c
DailyQuota 二级闸(单品 1000/日、店铺列表 300/日,0=关;出错放行不误伤); 店铺列表 page_size 上限 50→20(客户端固定传 20 无破坏);公开响应敏感字段 (cost/purchase_price/profit)零暴露回归测试。 明确不做:签名链接(QR 已印刷+UUIDv4 不可枚举)、滑块(杀零门槛分享)、登录墙。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
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())
|
|
}
|