feat: add per-hop pool references in listener chains
Allow listeners to mix named pools in a single chain using pool:name syntax. Bare "pool" continues to use the listener's default pool. Replaces pool_hops field with pool_seq list; pool_hops is now a backward-compatible property. Each hop draws from its own pool and failure reporting targets the correct source pool. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -407,6 +407,110 @@ class TestListenerConfig:
|
||||
assert c.listeners[0].chain == []
|
||||
|
||||
|
||||
class TestPoolSeq:
|
||||
"""Test per-hop pool references (pool:name syntax)."""
|
||||
|
||||
def test_bare_pool_uses_default_name(self, tmp_path):
|
||||
"""Bare `pool` + `pool: clean` -> pool_seq=["clean"]."""
|
||||
cfg_file = tmp_path / "test.yaml"
|
||||
cfg_file.write_text(
|
||||
"listeners:\n"
|
||||
" - listen: 1080\n"
|
||||
" pool: clean\n"
|
||||
" chain:\n"
|
||||
" - pool\n"
|
||||
)
|
||||
c = load_config(cfg_file)
|
||||
assert c.listeners[0].pool_seq == ["clean"]
|
||||
|
||||
def test_bare_pool_no_pool_name(self, tmp_path):
|
||||
"""Bare `pool` with no `pool:` key -> pool_seq=["default"]."""
|
||||
cfg_file = tmp_path / "test.yaml"
|
||||
cfg_file.write_text(
|
||||
"listeners:\n"
|
||||
" - listen: 1080\n"
|
||||
" chain:\n"
|
||||
" - pool\n"
|
||||
)
|
||||
c = load_config(cfg_file)
|
||||
assert c.listeners[0].pool_seq == ["default"]
|
||||
|
||||
def test_pool_colon_name(self, tmp_path):
|
||||
"""`pool:clean, pool:mitm` -> pool_seq=["clean", "mitm"]."""
|
||||
cfg_file = tmp_path / "test.yaml"
|
||||
cfg_file.write_text(
|
||||
"listeners:\n"
|
||||
" - listen: 1080\n"
|
||||
" chain:\n"
|
||||
" - pool:clean\n"
|
||||
" - pool:mitm\n"
|
||||
)
|
||||
c = load_config(cfg_file)
|
||||
assert c.listeners[0].pool_seq == ["clean", "mitm"]
|
||||
|
||||
def test_mixed_bare_and_named(self, tmp_path):
|
||||
"""Bare `pool` + `pool:mitm` with `pool: clean` -> ["clean", "mitm"]."""
|
||||
cfg_file = tmp_path / "test.yaml"
|
||||
cfg_file.write_text(
|
||||
"listeners:\n"
|
||||
" - listen: 1080\n"
|
||||
" pool: clean\n"
|
||||
" chain:\n"
|
||||
" - pool\n"
|
||||
" - pool:mitm\n"
|
||||
)
|
||||
c = load_config(cfg_file)
|
||||
assert c.listeners[0].pool_seq == ["clean", "mitm"]
|
||||
|
||||
def test_pool_colon_case_insensitive_prefix(self, tmp_path):
|
||||
"""`Pool:MyPool` -> pool_seq=["MyPool"] (prefix case-insensitive)."""
|
||||
cfg_file = tmp_path / "test.yaml"
|
||||
cfg_file.write_text(
|
||||
"listeners:\n"
|
||||
" - listen: 1080\n"
|
||||
" chain:\n"
|
||||
" - Pool:MyPool\n"
|
||||
)
|
||||
c = load_config(cfg_file)
|
||||
assert c.listeners[0].pool_seq == ["MyPool"]
|
||||
|
||||
def test_pool_colon_empty_is_bare(self, tmp_path):
|
||||
"""`pool:` (empty name) -> treated as bare pool."""
|
||||
cfg_file = tmp_path / "test.yaml"
|
||||
cfg_file.write_text(
|
||||
"listeners:\n"
|
||||
" - listen: 1080\n"
|
||||
" pool: clean\n"
|
||||
" chain:\n"
|
||||
" - pool:\n"
|
||||
)
|
||||
c = load_config(cfg_file)
|
||||
assert c.listeners[0].pool_seq == ["clean"]
|
||||
|
||||
def test_backward_compat_pool_hops_property(self):
|
||||
"""pool_hops property returns len(pool_seq)."""
|
||||
lc = ListenerConfig(pool_seq=["clean", "mitm"])
|
||||
assert lc.pool_hops == 2
|
||||
lc2 = ListenerConfig()
|
||||
assert lc2.pool_hops == 0
|
||||
|
||||
def test_legacy_auto_append(self, tmp_path):
|
||||
"""Singular `proxy_pool:` -> pool_seq=["default"]."""
|
||||
cfg_file = tmp_path / "test.yaml"
|
||||
cfg_file.write_text(
|
||||
"listen: 0.0.0.0:1080\n"
|
||||
"chain:\n"
|
||||
" - socks5://127.0.0.1:9050\n"
|
||||
"proxy_pool:\n"
|
||||
" sources:\n"
|
||||
" - url: http://api:8081/proxies\n"
|
||||
)
|
||||
c = load_config(cfg_file)
|
||||
lc = c.listeners[0]
|
||||
assert lc.pool_seq == ["default"]
|
||||
assert lc.pool_hops == 1
|
||||
|
||||
|
||||
class TestListenerBackwardCompat:
|
||||
"""Test backward-compatible single listener from old format."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user