- 参考 dygou_go 新增 internal/score/baseline.go:维护 scores.json 并支持原子写盘 - 查询积分时自动保存各账号基线余额;抢购后提供 ReconcileAccounts 刚性比对扣分差值 - App.go 与前端增加 ReconcileScores 绑定与【刚性对账】按钮,解决超时暗中成功漏单痛点 - 补充 BaselineStore 读写、落盘重载与 ReconcileAccounts 完整单测并通过前端编译
125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
package score
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"huya-go/internal/huya"
|
|
"huya-go/internal/sniper"
|
|
)
|
|
|
|
type mockScoreClient struct {
|
|
scores map[int64]int64
|
|
}
|
|
|
|
func (m *mockScoreClient) GetActPrizeList(ctx context.Context, uid int64, cookie string, sid int32) (*huya.GetActPrizeListResp, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *mockScoreClient) QueryUserScore(ctx context.Context, uid int64, cookie string, sid int32) (*huya.GetUserScoreResp, error) {
|
|
s := m.scores[uid]
|
|
return &huya.GetUserScoreResp{
|
|
Status: 200,
|
|
Msg: "请求成功",
|
|
NewGainScore: s,
|
|
NewUsedScore: 0,
|
|
}, nil
|
|
}
|
|
func (m *mockScoreClient) GetUserPrizeRecords(ctx context.Context, uid int64, cookie string, sid int32) (*huya.GetUserPrizeRecordsResp, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *mockScoreClient) ScoreExchangePrize(ctx context.Context, uid int64, cookie string, sid, pid int32) (*huya.ScoreExchangePrizeResp, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func TestBaselineStoreCRUDAndFlush(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
path := filepath.Join(tmpDir, "scores.json")
|
|
|
|
store := NewBaselineStore(path)
|
|
store.Set(1001, 500)
|
|
store.SetMany(map[int64]int64{
|
|
1002: 800,
|
|
1003: 1200,
|
|
})
|
|
|
|
score, ok, at := store.Get(1002)
|
|
if !ok || score != 800 {
|
|
t.Fatalf("Get(1002) = %d, ok=%v; want 800, true", score, ok)
|
|
}
|
|
if at.IsZero() {
|
|
t.Fatalf("at is zero")
|
|
}
|
|
|
|
snap := store.Snapshot()
|
|
if len(snap) != 3 || snap[1001] != 500 || snap[1003] != 1200 {
|
|
t.Fatalf("Snapshot invalid: %v", snap)
|
|
}
|
|
|
|
// 验证落盘与重新加载
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("scores.json should exist: %v", err)
|
|
}
|
|
|
|
newStore := NewBaselineStore(path)
|
|
newStore.Load()
|
|
if s, ok, _ := newStore.Get(1003); !ok || s != 1200 {
|
|
t.Fatalf("Reload failed: got %d, %v", s, ok)
|
|
}
|
|
}
|
|
|
|
func TestReconcileAccounts(t *testing.T) {
|
|
store := NewBaselineStore("")
|
|
// 基线:1001号 1000分,1002号 500分
|
|
store.Set(1001, 1000)
|
|
store.Set(1002, 500)
|
|
|
|
// 当前:1001号剩 700分(扣300),1002号剩 500分(未扣),1003号剩 800分(无基线)
|
|
client := &mockScoreClient{
|
|
scores: map[int64]int64{
|
|
1001: 700,
|
|
1002: 500,
|
|
1003: 800,
|
|
},
|
|
}
|
|
|
|
accounts := []sniper.Account{
|
|
{UID: 1001, Cookie: "yyuid=1001"},
|
|
{UID: 1002, Cookie: "yyuid=1002"},
|
|
{UID: 1003, Cookie: "yyuid=1003"},
|
|
}
|
|
|
|
results, err := ReconcileAccounts(context.Background(), client, store, 2277, 300, accounts, 5)
|
|
if err != nil {
|
|
t.Fatalf("ReconcileAccounts error: %v", err)
|
|
}
|
|
if len(results) != 3 {
|
|
t.Fatalf("expected 3 results, got %d", len(results))
|
|
}
|
|
|
|
// 验证 1001 刚性确认成功
|
|
r1 := results[0]
|
|
if !r1.Confirmed || r1.DiffScore != 300 || r1.CurrentScore != 700 {
|
|
t.Fatalf("r1 unexpected: %+v", r1)
|
|
}
|
|
|
|
// 验证 1002 未变动
|
|
r2 := results[1]
|
|
if r2.Confirmed || r2.DiffScore != 0 {
|
|
t.Fatalf("r2 unexpected: %+v", r2)
|
|
}
|
|
|
|
// 验证 1003 无基线
|
|
r3 := results[2]
|
|
if r3.HasBaseline || r3.Confirmed {
|
|
t.Fatalf("r3 unexpected: %+v", r3)
|
|
}
|
|
|
|
// 验证对账后基线已自动更新为最新值
|
|
s1, _, _ := store.Get(1001)
|
|
if s1 != 700 {
|
|
t.Fatalf("expected updated baseline 700, got %d", s1)
|
|
}
|
|
}
|