feat: 按允许店铺筛选顺运宝同步 (#196)

This commit is contained in:
chengma
2026-08-12 18:48:31 +08:00
parent 4089033453
commit 8a8fff8480
17 changed files with 720 additions and 30 deletions
+112 -3
View File
@@ -5,8 +5,11 @@ import (
"database/sql"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"strings"
"testing"
"time"
@@ -198,6 +201,9 @@ func fakeSybServer(t *testing.T, stocks []fakeStock, failListPageIndex int) *htt
if stocks[i].Created == "" {
stocks[i].Created = "2026-07-28"
}
if stocks[i].ShopName == "" {
stocks[i].ShopName = "测试店铺"
}
s := stocks[i]
byID[s.ID] = s
}
@@ -315,6 +321,16 @@ type integrityServerData struct {
func fakeIntegritySybServer(t *testing.T, data integrityServerData) *httptest.Server {
t.Helper()
for _, row := range data.list {
if _, ok := row["shopName"]; !ok {
row["shopName"] = "测试店铺"
}
}
for _, row := range data.details {
if _, ok := row["shopName"]; !ok {
row["shopName"] = "测试店铺"
}
}
totalCalls := 0
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
@@ -349,7 +365,100 @@ func writeEnvelope(t *testing.T, w http.ResponseWriter, status bool, msg string,
func newSyncTestDB(t *testing.T) *sql.DB {
t.Helper()
return newTestDB(t)
db := newTestDB(t)
now := model.NowISO()
if err := repository.CreateUser(db, model.User{
UserID: "SYB-TEST-ADMIN", Username: "syb-test-admin", PasswordHash: "test",
Role: model.RoleAdmin, Status: model.UserActive, PasswordChangedAt: now, CreatedAt: now, UpdatedAt: now,
}); err != nil {
t.Fatalf("准备同步测试管理员失败: %v", err)
}
if err := repository.InsertSybAllowedShop(db, model.SybAllowedShop{
ShopID: "SYB-TEST-SHOP", ShopName: "测试店铺", NormalizedName: "测试店铺", Enabled: true,
CreatedByUserID: "SYB-TEST-ADMIN", CreatedAt: now, UpdatedAt: now,
}); err != nil {
t.Fatalf("准备同步测试允许店铺失败: %v", err)
}
return db
}
func TestRunSybSync_没有启用店铺时不请求顺运宝(t *testing.T) {
db := newSyncTestDB(t)
if _, err := db.Exec(`UPDATE syb_allowed_shops SET enabled=0`); err != nil {
t.Fatal(err)
}
requests := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
w.WriteHeader(http.StatusInternalServerError)
}))
defer srv.Close()
client, _ := syb.New(srv.URL)
report := RunSybSyncWithOptions(context.Background(), db, client,
config.SybConfig{BaseURL: srv.URL, PageSize: 20, MaxMatches: 100}, time.Now(),
SybSyncOptions{From: "2026-08-11", To: "2026-08-11"})
if report.Err == nil || !strings.Contains(report.Err.Error(), "没有启用") {
t.Fatalf("实际错误: %v", report.Err)
}
if requests != 0 {
t.Fatalf("空白名单不应请求顺运宝,实际 %d 次", requests)
}
if report.CursorAdvanced {
t.Fatal("空白名单不得推进游标")
}
}
func TestSybShopAllowed_明细店铺覆盖列表后重新拦截(t *testing.T) {
allowed := map[string]struct{}{"测试店铺": {}}
if !sybShopAllowed(allowed, map[string]any{"shopName": " 测试店铺 "}, nil) {
t.Fatal("应忽略允许店铺名称首尾空白")
}
if sybShopAllowed(allowed, map[string]any{"shopName": "测试店铺"}, map[string]any{"shopName": "其他店铺"}) {
t.Fatal("明细店铺变化后必须重新拦截")
}
if sybShopAllowed(allowed, map[string]any{"shopName": "测试店铺"}, map[string]any{"shopName": " "}) {
t.Fatal("明细店铺变为空值时必须拦截")
}
}
func TestRunSybSync_只请求并写入允许店铺(t *testing.T) {
db := newSyncTestDB(t)
detailIDs := []int64{}
stocks := []fakeStock{
{ID: 1, Code: "A", ShopName: " 测试店铺 ", Details: []fakeDetail{{ID: 11, ProductID: 111, ProductQty: 1}}},
{ID: 2, Code: "B", ShopName: "其他店铺", Details: []fakeDetail{{ID: 22, ProductID: 222, ProductQty: 1}}},
}
base := fakeSybServer(t, stocks, 0)
defer base.Close()
target, _ := url.Parse(base.URL)
reverseProxy := httputil.NewSingleHostReverseProxy(target)
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/am/stock/detail/listByStock" {
var body struct {
IDs []int64 `json:"ids"`
}
data, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(data, &body)
detailIDs = append(detailIDs, body.IDs...)
r.Body = io.NopCloser(strings.NewReader(string(data)))
}
reverseProxy.ServeHTTP(w, r)
}))
defer proxy.Close()
client, _ := syb.New(proxy.URL)
report := RunSybSyncWithOptions(context.Background(), db, client,
config.SybConfig{BaseURL: proxy.URL, PageSize: 20, MaxMatches: 100},
time.Date(2026, 7, 28, 12, 0, 0, 0, time.UTC),
SybSyncOptions{From: "2026-07-28", To: "2026-07-28"})
if report.Err != nil {
t.Fatal(report.Err)
}
if report.StockCount != 2 || report.AcceptedCount != 1 || report.ShopSkipped != 1 || report.Created != 1 {
t.Fatalf("店铺统计不正确: %+v", report)
}
if len(detailIDs) != 1 || detailIDs[0] != 1 {
t.Fatalf("明细请求应只有允许店铺,实际 %v", detailIDs)
}
}
func TestRunSybSync_已有的ShopeeSKUID同步后仍在(t *testing.T) {
@@ -789,7 +898,7 @@ func fakeGrowingTodaySybServer(t *testing.T, today string, growAttempts int) (*h
rows := make([]map[string]any, 0, count)
for i := 1; i <= count; i++ {
id := idOffset + int64(i)
rows = append(rows, map[string]any{"id": id, "code": fmt.Sprintf("ORDER-%d", id)})
rows = append(rows, map[string]any{"id": id, "code": fmt.Sprintf("ORDER-%d", id), "shopName": "测试店铺"})
}
return rows
}
@@ -833,7 +942,7 @@ func fakeGrowingTodaySybServer(t *testing.T, today string, growAttempts int) (*h
list := make([]map[string]any, 0, len(body.IDs))
for _, id := range body.IDs {
list = append(list, map[string]any{
"id": id, "code": fmt.Sprintf("ORDER-%d", id),
"id": id, "code": fmt.Sprintf("ORDER-%d", id), "shopName": "测试店铺",
"details": []map[string]any{{
"id": id + 1000, "productId": id + 10000, "productQty": 1,
}},