Files
cmautobuy/admin/handler/integration/auth_test.go
T

115 lines
4.9 KiB
Go
Raw Normal View History

2026-08-11 10:00:07 +08:00
package integration
import (
"bytes"
2026-08-11 10:11:15 +08:00
"database/sql"
2026-08-11 10:00:07 +08:00
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"cmautobuy/admin/config"
2026-08-11 10:11:15 +08:00
"cmautobuy/admin/model"
"cmautobuy/admin/repository"
_ "modernc.org/sqlite"
2026-08-11 10:00:07 +08:00
)
func TestRequireCatalogToken(t *testing.T) {
gin.SetMode(gin.TestMode)
const token = "0123456789abcdef0123456789abcdef"
for _, tc := range []struct {
name, header string
cfg config.CatalogIntegrationConfig
want int
}{
{"未启用", "", config.CatalogIntegrationConfig{}, http.StatusServiceUnavailable},
{"缺少", "", config.CatalogIntegrationConfig{Source: "script", Token: token}, http.StatusUnauthorized},
{"错误", "Bearer wrong", config.CatalogIntegrationConfig{Source: "script", Token: token}, http.StatusUnauthorized},
{"正确", "Bearer " + token, config.CatalogIntegrationConfig{Source: "script", Token: token}, http.StatusNoContent},
} {
t.Run(tc.name, func(t *testing.T) {
r := gin.New()
r.GET("/protected", RequireCatalogToken(tc.cfg), func(c *gin.Context) {
if integrationSource(c) != tc.cfg.Source {
t.Fatalf("source=%q", integrationSource(c))
}
c.Status(http.StatusNoContent)
})
req := httptest.NewRequest(http.MethodGet, "/protected", nil)
req.Header.Set("Authorization", tc.header)
resp := httptest.NewRecorder()
r.ServeHTTP(resp, req)
if resp.Code != tc.want {
t.Fatalf("status=%d body=%s", resp.Code, resp.Body.String())
}
if strings.Contains(resp.Body.String(), token) || strings.Contains(resp.Body.String(), "wrong") {
t.Fatal("响应泄露接口凭据")
}
})
}
}
2026-08-11 10:11:15 +08:00
func TestCatalogBatchAPI_按批次查询摘要(t *testing.T) {
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
2026-08-11 11:18:51 +08:00
_, err = db.Exec(`CREATE TABLE catalog_import_runs(source TEXT,batch_id TEXT,request_hash TEXT,status TEXT,request_count INTEGER,conflict_count INTEGER,observed_at TEXT,update_policy TEXT DEFAULT 'fill_missing',last_request_at TEXT,last_conflict_at TEXT,shopee_created INTEGER DEFAULT 0,shopee_updated INTEGER DEFAULT 0,shopee_fields_filled INTEGER DEFAULT 0,shopee_fields_same_source_updated INTEGER DEFAULT 0,shopee_fields_manual_skipped INTEGER DEFAULT 0,shopee_fields_stale_skipped INTEGER DEFAULT 0,sku_created INTEGER DEFAULT 0,sku_updated INTEGER DEFAULT 0,sku_filled INTEGER DEFAULT 0,sku_same_source_updated INTEGER DEFAULT 0,sku_skipped INTEGER DEFAULT 0,sku_manual_skipped INTEGER DEFAULT 0,sku_stale_skipped INTEGER DEFAULT 0,pdd_created INTEGER DEFAULT 0,pdd_updated INTEGER DEFAULT 0,association_created INTEGER DEFAULT 0,association_unchanged INTEGER DEFAULT 0,failure_count INTEGER DEFAULT 0,error_summary TEXT,response_body TEXT,created_at TEXT,finished_at TEXT,PRIMARY KEY(source,batch_id))`)
2026-08-11 10:11:15 +08:00
if err != nil {
t.Fatal(err)
}
run := model.CatalogImportRun{Source: "script", BatchID: "B-1", RequestHash: "abc", Status: model.CatalogImportSucceeded, LastRequestAt: model.NowISO(), CreatedAt: model.NowISO()}
if err := repository.InsertCatalogImportRun(db, run); err != nil {
t.Fatal(err)
}
const token = "0123456789abcdef0123456789abcdef"
r := gin.New()
Register(r, db, config.CatalogIntegrationConfig{Source: "script", Token: token})
req := httptest.NewRequest(http.MethodGet, "/api/v1/integrations/catalog/batches/B-1", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp := httptest.NewRecorder()
r.ServeHTTP(resp, req)
if resp.Code != 200 || !strings.Contains(resp.Body.String(), `"batch_id":"B-1"`) {
t.Fatalf("status=%d body=%s", resp.Code, resp.Body.String())
}
2026-08-11 11:03:18 +08:00
if !strings.Contains(resp.Body.String(), `"sku_filled":0`) || !strings.Contains(resp.Body.String(), `"update_policy"`) {
t.Fatal("批次摘要缺少更新策略统计")
}
2026-08-11 10:11:15 +08:00
if strings.Contains(resp.Body.String(), "request_hash") {
t.Fatal("查询响应不应暴露请求哈希")
}
}
func TestCatalogBatchAPI_鉴权JSON与体积限制(t *testing.T) {
gin.SetMode(gin.TestMode)
const token = "0123456789abcdef0123456789abcdef"
r := gin.New()
Register(r, nil, config.CatalogIntegrationConfig{Source: "script", Token: token})
for _, tc := range []struct {
name, auth string
body []byte
want int
}{
{"无鉴权", "", []byte(`{}`), http.StatusUnauthorized},
{"坏JSON", "Bearer " + token, []byte(`{"schema_version":`), http.StatusBadRequest},
{"超限", "Bearer " + token, bytes.Repeat([]byte("x"), catalogMaxBody+1), http.StatusRequestEntityTooLarge},
} {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/integrations/catalog/batches", bytes.NewReader(tc.body))
req.Header.Set("Authorization", tc.auth)
resp := httptest.NewRecorder()
r.ServeHTTP(resp, req)
if resp.Code != tc.want {
t.Fatalf("status=%d body=%s", resp.Code, resp.Body.String())
}
if !strings.Contains(resp.Header().Get("Content-Type"), "application/json") {
t.Fatalf("错误响应必须是 JSON:%s", resp.Header())
}
})
}
}