Files
luotianyi 6aae7b65ea feat(proxypool): 站点级出口代理池(Xray / 订阅 / 端点,粘性选路与健康探测)
- proxypool: 站点级出口绑定 proxies = [条目名, "sub:订阅名"],粘性选路、
  失败切换、后台健康探测;支持 Xray 分享链接 / 订阅 / 裸 ip:port 与 socks5 端点,
  托管 Xray 核心(可自动下载);站点未绑定时回退 [proxy] 主/备线路
- upstream: RouteSource 抽象 + routesFor/poolReport;求解与请求共用同一出口
- admin: 代理池面板与维护动作;overview 暴露池状态
2026-10-08 00:19:39 +08:00

77 lines
1.9 KiB
Go

package proxypool
import (
"testing"
"github.com/nyoungo/dsfree2api/internal/config"
)
const examplePath = "../../config.example.toml"
func TestCandidatesStickySwitchOnFailure(t *testing.T) {
cfg, err := config.Load(examplePath)
if err != nil {
t.Fatalf("load config: %v", err)
}
cfg.ProxyPool.Enabled = true
cfg.ProxyPool.Entries = map[string]*config.ProxyEntry{
"a": {Link: "http://127.0.0.1:9", Enabled: true},
"b": {Link: "socks5://127.0.0.1:10", Enabled: true},
}
cfg.Sites["de"].Proxies = []string{"a", "b"}
m := New(cfg, t.TempDir(), nil)
m.Reload()
cands := m.Candidates("de")
if len(cands) != 2 || cands[0].Name != "a" || cands[0].Proxy != "http://127.0.0.1:9" {
t.Fatalf("candidates = %+v", cands)
}
// Both healthy: the sticky pick stays a.
m.mu.Lock()
for _, ep := range m.endpoints {
ep.checked = true
ep.healthy = true
}
m.mu.Unlock()
cands = m.Candidates("de")
if cands[0].Name != "a" {
t.Fatalf("sticky pick changed without failure: %+v", cands)
}
// a fails: the request path must immediately switch to b.
m.Report("http://127.0.0.1:9", false)
cands = m.Candidates("de")
if len(cands) != 1 || cands[0].Name != "b" {
t.Fatalf("after failure = %+v", cands)
}
// Everything unhealthy: fall back to the bound order so requests still try.
m.Report("socks5://127.0.0.1:10", false)
cands = m.Candidates("de")
if len(cands) != 2 || cands[0].Name != "a" {
t.Fatalf("all-unhealthy fallback = %+v", cands)
}
}
func TestCandidatesDisabledOrUnbound(t *testing.T) {
cfg, err := config.Load(examplePath)
if err != nil {
t.Fatalf("load config: %v", err)
}
cfg.ProxyPool.Enabled = false
cfg.Sites["de"].Proxies = []string{"a"}
m := New(cfg, t.TempDir(), nil)
m.Reload()
if got := m.Candidates("de"); got != nil {
t.Fatalf("disabled pool returned %+v", got)
}
cfg.ProxyPool.Enabled = true
m.Reload()
if got := m.Candidates("de"); got != nil {
t.Fatalf("unbound/unknown entry returned %+v", got)
}
}