fix: 稳定顺运宝当天分页同步 (#192)
This commit is contained in:
+169
-17
@@ -120,33 +120,23 @@ func TestNewSybSyncOptions_指定日期校验(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultSybSyncRange_最近三天与缺口分段(t *testing.T) {
|
||||
func TestDefaultSybSyncRange_固定为昨天到今天(t *testing.T) {
|
||||
now := time.Date(2026, 8, 9, 12, 0, 0, 0, time.UTC)
|
||||
t.Run("没有游标默认最近三天", func(t *testing.T) {
|
||||
t.Run("没有游标", func(t *testing.T) {
|
||||
db := newSyncTestDB(t)
|
||||
got, err := DefaultSybSyncRange(db, now)
|
||||
if err != nil || got.From != "2026-08-07" || got.To != "2026-08-09" || got.Warning != "" {
|
||||
if err != nil || got.From != "2026-08-08" || got.To != "2026-08-09" || got.Warning != "" {
|
||||
t.Fatalf("默认范围错误: got=%+v err=%v", got, err)
|
||||
}
|
||||
})
|
||||
t.Run("短缺口自动扩展到今天", func(t *testing.T) {
|
||||
db := newSyncTestDB(t)
|
||||
if err := repository.SetSybLastSyncedAt(db, "2026-08-05T00:00:00Z"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := DefaultSybSyncRange(db, now)
|
||||
if err != nil || got.From != "2026-08-05" || got.To != "2026-08-09" || got.Warning == "" {
|
||||
t.Fatalf("短缺口范围错误: got=%+v err=%v", got, err)
|
||||
}
|
||||
})
|
||||
t.Run("长缺口优先选择最早三十一天", func(t *testing.T) {
|
||||
t.Run("旧游标不改写页面默认范围", func(t *testing.T) {
|
||||
db := newSyncTestDB(t)
|
||||
if err := repository.SetSybLastSyncedAt(db, "2026-07-01T00:00:00Z"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := DefaultSybSyncRange(db, now)
|
||||
if err != nil || got.From != "2026-07-01" || got.To != "2026-07-31" || !strings.Contains(got.Warning, "继续同步下一段") {
|
||||
t.Fatalf("长缺口分段错误: got=%+v err=%v", got, err)
|
||||
if err != nil || got.From != "2026-08-08" || got.To != "2026-08-09" || got.Warning != "" {
|
||||
t.Fatalf("有旧游标时默认范围错误: got=%+v err=%v", got, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -772,6 +762,168 @@ func TestRunSybSync_跨日范围按天同步并汇总(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type growingTodayServerState struct {
|
||||
historyListCalls int
|
||||
todayListCalls int
|
||||
currentToday int
|
||||
}
|
||||
|
||||
// fakeGrowingTodaySybServer 模拟“历史日稳定、今天在每次分页时新增一张单”。
|
||||
// growAttempts=2 表示前两次漂移、第三次稳定;=3 表示三次都漂移。
|
||||
func fakeGrowingTodaySybServer(t *testing.T, today string, growAttempts int) (*httptest.Server, *growingTodayServerState) {
|
||||
t.Helper()
|
||||
state := &growingTodayServerState{currentToday: 1}
|
||||
requestDate := func(body map[string]any) string {
|
||||
queries, ok := body["queries"].([]any)
|
||||
if !ok || len(queries) == 0 {
|
||||
t.Fatalf("同步请求缺少 queries: %#v", body)
|
||||
}
|
||||
query, ok := queries[0].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("同步请求日期条件格式错误: %#v", queries[0])
|
||||
}
|
||||
rangeText, _ := query["dvalue"].(string)
|
||||
return strings.SplitN(rangeText, ",", 2)[0]
|
||||
}
|
||||
listRows := func(count int, idOffset int64) []map[string]any {
|
||||
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)})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/am/stock/listTotal":
|
||||
var body map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("解析 listTotal 请求失败: %v", err)
|
||||
}
|
||||
if requestDate(body) == today {
|
||||
writeEnvelope(t, w, true, "ok", state.currentToday, nil)
|
||||
} else {
|
||||
writeEnvelope(t, w, true, "ok", 1, nil)
|
||||
}
|
||||
case "/am/stock/list":
|
||||
var body map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("解析 list 请求失败: %v", err)
|
||||
}
|
||||
if requestDate(body) == today {
|
||||
state.todayListCalls++
|
||||
if state.todayListCalls <= growAttempts {
|
||||
state.currentToday++
|
||||
}
|
||||
rows := listRows(state.currentToday, 0)
|
||||
writeEnvelope(t, w, true, "ok", map[string]any{"list": rows, "total": len(rows)}, nil)
|
||||
} else {
|
||||
state.historyListCalls++
|
||||
rows := listRows(1, 100)
|
||||
writeEnvelope(t, w, true, "ok", map[string]any{"list": rows, "total": len(rows)}, nil)
|
||||
}
|
||||
case "/am/stock/detail/listByStock":
|
||||
var body struct {
|
||||
IDs []int64 `json:"ids"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("解析明细请求失败: %v", err)
|
||||
}
|
||||
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),
|
||||
"details": []map[string]any{{
|
||||
"id": id + 1000, "productId": id + 10000, "productQty": 1,
|
||||
}},
|
||||
})
|
||||
}
|
||||
writeEnvelope(t, w, true, "ok", map[string]any{"list": list}, nil)
|
||||
default:
|
||||
t.Errorf("测试假服务端没有实现这个路径: %s", r.URL.Path)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
return srv, state
|
||||
}
|
||||
|
||||
func TestRunSybSync_今天前两次漂移第三次稳定只重试今天(t *testing.T) {
|
||||
const today = "2026-08-12"
|
||||
srv, state := fakeGrowingTodaySybServer(t, today, 2)
|
||||
defer srv.Close()
|
||||
db := newSyncTestDB(t)
|
||||
client, _ := syb.New(srv.URL)
|
||||
|
||||
report := RunSybSyncWithOptions(context.Background(), db, client,
|
||||
config.SybConfig{BaseURL: srv.URL, PageSize: 20, MaxMatches: 100},
|
||||
time.Date(2026, 8, 12, 8, 0, 0, 0, time.UTC),
|
||||
SybSyncOptions{From: "2026-08-11", To: today})
|
||||
if report.Err != nil {
|
||||
t.Fatalf("第三次稳定后应该成功: %v", report.Err)
|
||||
}
|
||||
if state.historyListCalls != 1 || state.todayListCalls != 3 {
|
||||
t.Fatalf("只应重试今天: history=%d today=%d", state.historyListCalls, state.todayListCalls)
|
||||
}
|
||||
if report.StockCount != 4 || report.Created != 4 || !report.CursorAdvanced {
|
||||
t.Fatalf("稳定后的统计或游标错误: %+v", report)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSybSync_今天连续三次漂移保存最后一次完整明细但不推进游标(t *testing.T) {
|
||||
const today = "2026-08-12"
|
||||
srv, state := fakeGrowingTodaySybServer(t, today, 3)
|
||||
defer srv.Close()
|
||||
db := newSyncTestDB(t)
|
||||
client, _ := syb.New(srv.URL)
|
||||
|
||||
report := RunSybSyncWithOptions(context.Background(), db, client,
|
||||
config.SybConfig{BaseURL: srv.URL, PageSize: 20, MaxMatches: 100},
|
||||
time.Date(2026, 8, 12, 8, 0, 0, 0, time.UTC),
|
||||
SybSyncOptions{From: "2026-08-11", To: today})
|
||||
if report.Err == nil || !strings.Contains(report.Err.Error(), "连续 3 次") ||
|
||||
!strings.Contains(report.Err.Error(), "下次同步将继续覆盖当天") {
|
||||
t.Fatalf("持续漂移应该返回可操作提示,实际: %v", report.Err)
|
||||
}
|
||||
if state.historyListCalls != 1 || state.todayListCalls != 3 {
|
||||
t.Fatalf("只应重试今天: history=%d today=%d", state.historyListCalls, state.todayListCalls)
|
||||
}
|
||||
if report.StockCount != 5 || report.Created != 5 || report.CursorAdvanced {
|
||||
t.Fatalf("应保存历史 1 张和最后一次当天 4 张,但不推进游标: %+v", report)
|
||||
}
|
||||
if total, err := repository.CountSybOrdersTotal(db); err != nil || total != 5 {
|
||||
t.Fatalf("已取得的完整明细应落库: total=%d err=%v", total, err)
|
||||
}
|
||||
if _, found, err := repository.GetSybLastSyncedAt(db); err != nil || found {
|
||||
t.Fatalf("持续漂移不得建立或推进游标: found=%v err=%v", found, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSybSync_今天重试增长后仍受MaxMatches限制(t *testing.T) {
|
||||
const today = "2026-08-12"
|
||||
srv, state := fakeGrowingTodaySybServer(t, today, 3)
|
||||
defer srv.Close()
|
||||
db := newSyncTestDB(t)
|
||||
client, _ := syb.New(srv.URL)
|
||||
|
||||
report := RunSybSyncWithOptions(context.Background(), db, client,
|
||||
config.SybConfig{BaseURL: srv.URL, PageSize: 20, MaxMatches: 3},
|
||||
time.Date(2026, 8, 12, 8, 0, 0, 0, time.UTC),
|
||||
SybSyncOptions{From: "2026-08-11", To: today})
|
||||
if report.Err == nil || !strings.Contains(report.Err.Error(), "超过单次同步上限") {
|
||||
t.Fatalf("今天增长突破上限时应该停止,实际: %v", report.Err)
|
||||
}
|
||||
if state.historyListCalls != 1 || state.todayListCalls != 2 {
|
||||
t.Fatalf("超限后不应继续重试: history=%d today=%d", state.historyListCalls, state.todayListCalls)
|
||||
}
|
||||
if total, err := repository.CountSybOrdersTotal(db); err != nil || total != 1 {
|
||||
t.Fatalf("只应保留超限前已完成的历史数据: total=%d err=%v", total, err)
|
||||
}
|
||||
if report.CursorAdvanced {
|
||||
t.Fatal("今天增长超限不得推进游标")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDetailBatch_必须与请求货运单一一对应(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -837,7 +989,7 @@ func TestRunSybSync_列表或明细不完整时不推进游标(t *testing.T) {
|
||||
client, _ := syb.New(srv.URL)
|
||||
report := RunSybSyncWithOptions(context.Background(), db, client,
|
||||
config.SybConfig{BaseURL: srv.URL, PageSize: 20, MaxMatches: 100, SyncFrom: "2026-07-28"},
|
||||
time.Date(2026, 7, 28, 12, 0, 0, 0, time.UTC),
|
||||
time.Date(2026, 7, 29, 12, 0, 0, 0, time.UTC),
|
||||
SybSyncOptions{From: "2026-07-28", To: "2026-07-28"})
|
||||
if report.Err == nil || !strings.Contains(report.Err.Error(), tc.wantErr) {
|
||||
t.Fatalf("错误应包含 %q,实际 %v", tc.wantErr, report.Err)
|
||||
|
||||
Reference in New Issue
Block a user