Files
yml2213 23f964e768 feat(config): 将默认活动 SID 更新为最新活动 2277
- 后端默认值与测试:DefaultConfig 默认 SID 改为 2277(旧活动 2203 已停止返回商品),更新单元测试断言
- 前端默认值与平滑迁移:pickConfig 默认 SID 设为 2277,localStorage 自动将旧版 2203 迁移至 2277
- 验证:Go 单测与 Vite 前端打包干净通过
2026-10-17 18:58:38 +08:00

205 lines
5.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gui
import (
"testing"
"time"
"huya-go/internal/logger"
)
func TestSplitCookieLinesNormalizesLineBreaks(t *testing.T) {
tests := []struct {
name string
input string
want []string
}{
{
name: "crlf_and_cr",
input: "ck-a\r\nck-b\rck-c",
want: []string{"ck-a", "ck-b", "ck-c"},
},
{
name: "escaped_line_breaks",
input: `ck-a\r\nck-b\rck-c\nck-d`,
want: []string{"ck-a", "ck-b", "ck-c", "ck-d"},
},
{
name: "slash_r_line_breaks",
input: "ck-a/r/nck-b/rck-c/nck-d",
want: []string{"ck-a", "ck-b", "ck-c", "ck-d"},
},
{
name: "slash_n_only_is_preserved",
input: "ck-a/nck-b",
want: []string{"ck-a/nck-b"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
lines := splitCookieLines(tt.input)
if len(lines) != len(tt.want) {
t.Fatalf("len(splitCookieLines()) = %d, want %d", len(lines), len(tt.want))
}
for i, line := range lines {
if line.text != tt.want[i] {
t.Fatalf("line[%d] = %q, want %q", i, line.text, tt.want[i])
}
}
})
}
}
func TestParseAccountsRequiresExplicitSelection(t *testing.T) {
cfg := Config{
CookieText: "udb_uid=1; yyuid=1\nudb_uid=2; yyuid=2",
}
if _, err := parseAccounts(cfg); err == nil {
t.Fatal("空选择不应回退为全部账号")
}
cfg.SelectedAccountLines = []int{2}
accounts, err := parseAccounts(cfg)
if err != nil {
t.Fatalf("parseAccounts() error = %v", err)
}
if len(accounts) != 1 || accounts[0].UID != 2 {
t.Fatalf("accounts=%+v, want 仅第 2 行账号", accounts)
}
}
func TestDefaultConfigUsesWindowMode(t *testing.T) {
cfg := (&App{}).DefaultConfig()
if cfg.SID != 2277 {
t.Fatalf("SID=%d, want 2277(最新活动 SID)", cfg.SID)
}
if cfg.LeadMS != 0 {
t.Fatalf("LeadMS=%d, want 0(动态提前量)", cfg.LeadMS)
}
if cfg.Concurrency != 30 {
t.Fatalf("Concurrency=%d, want 30(单实例 30 账号全并发首发)", cfg.Concurrency)
}
if cfg.WindowSeconds != 30 {
t.Fatalf("WindowSeconds=%d, want 30", cfg.WindowSeconds)
}
if cfg.DelayMS != 120 {
t.Fatalf("DelayMS=%d, want 120", cfg.DelayMS)
}
if !cfg.Prewarm {
t.Fatal("Prewarm 应默认开启(提前 10 秒异步预热,不阻塞发令枪)")
}
}
func TestParseStartTimeAcceptsClockOnly(t *testing.T) {
t.Run("future_today", func(t *testing.T) {
now := time.Date(2026, 7, 6, 10, 20, 30, 0, time.Local)
got := nextClockTime(now, 12, 5, 6)
want := time.Date(2026, 7, 6, 12, 5, 6, 0, time.Local)
if !got.Equal(want) {
t.Fatalf("nextClockTime() = %s, want %s", got, want)
}
})
t.Run("past_tomorrow", func(t *testing.T) {
now := time.Date(2026, 7, 6, 10, 20, 30, 0, time.Local)
got := nextClockTime(now, 9, 5, 6)
want := time.Date(2026, 7, 7, 9, 5, 6, 0, time.Local)
if !got.Equal(want) {
t.Fatalf("nextClockTime() = %s, want %s", got, want)
}
})
t.Run("parse_hms", func(t *testing.T) {
got, err := parseStartTime("23:59:58")
if err != nil {
t.Fatalf("parseStartTime() error = %v", err)
}
if got.Hour() != 23 || got.Minute() != 59 || got.Second() != 58 {
t.Fatalf("parseStartTime() = %s, want 23:59:58", got)
}
})
}
func TestSetLogLevelNormalizesAndFilters(t *testing.T) {
dir := t.TempDir()
store := logger.NewStore(20)
closeLog, err := logger.Init(store, dir, "测试实例", "test", dir)
if err != nil {
t.Fatalf("logger.Init() error = %v", err)
}
defer closeLog()
defer logger.SetMinLevel(logger.LevelInfo)
app := &App{logStore: store}
if got := app.SetLogLevel("这不是合法等级"); got != logger.LevelInfo {
t.Fatalf("非法等级应回退为 info, got %q", got)
}
if got := app.SetLogLevel("debug"); got != logger.LevelDebug {
t.Fatalf("SetLogLevel(debug) = %q", got)
}
logger.LogEvent("网络", logger.LevelDebug, "debug 应被保留")
app.SetLogLevel("error")
logger.LogEvent("网络", logger.LevelDebug, "debug 应被过滤")
for _, entry := range store.Since(0) {
if entry.Message == "debug 应被过滤" {
t.Fatal("低于最低等级的日志不应进入内存缓冲")
}
}
}
func TestParseTargetsAppliesPriceAndInventoryFilters(t *testing.T) {
targets := parseTargets(Config{
PID: 12889,
Name: "小黄鸭",
TargetText: "13001\nauto",
AutoSelect: true,
MaxPrice: 1000,
MinLeft: 2,
})
if len(targets) != 4 {
t.Fatalf("targets=%d, want 4", len(targets))
}
for _, target := range targets {
if target.MaxPrice != 1000 {
t.Fatalf("target %+v maxPrice=%d, want 1000", target, target.MaxPrice)
}
if target.MinLeft != 2 {
t.Fatalf("target %+v minLeft=%d, want 2", target, target.MinLeft)
}
}
}
func TestParseTargetsWithoutFiltersKeepsZeroValues(t *testing.T) {
targets := parseTargets(Config{PID: 12889})
if len(targets) != 1 {
t.Fatalf("targets=%d, want 1", len(targets))
}
if targets[0].MaxPrice != 0 || targets[0].MinLeft != 0 {
t.Fatalf("unexpected filters: %+v", targets[0])
}
}
// TestRunnerConfigMapsDirectExchangeToExchangeMode 验证界面「直接兑换」开关能真正
// 切换到「先查列表」模式:引擎在 ListBeforeExchange=false 时会强制回落到直兑,
// 所以关闭直接兑换必须同时置 ListBeforeExchange,否则开关形同失效。
func TestRunnerConfigMapsDirectExchangeToExchangeMode(t *testing.T) {
direct := runnerConfig(Config{
SID: 2203,
DelayMS: 120,
Concurrency: 5,
DirectExchange: true,
})
if !direct.DirectExchange || direct.ListBeforeExchange {
t.Fatalf("直接兑换模式映射错误: %+v", direct)
}
if direct.SID != 2203 || direct.Delay != 120*time.Millisecond || direct.Concurrency != 5 {
t.Fatalf("基础字段未透传: %+v", direct)
}
list := runnerConfig(Config{SID: 2203, DirectExchange: false})
if list.DirectExchange || !list.ListBeforeExchange {
t.Fatalf("关闭直接兑换后应改为先查列表: %+v", list)
}
}