import yaml from dataclasses import dataclass from expander import filter_proxy, expand_config, build_mihomo_config, inject_providers_for_delivery @dataclass class FakeSub: name: str url: str PROXY_FULL = { "name": "node1", "type": "ss", "server": "1.2.3.4", "port": 443, "password": "secret", "cipher": "aes-256-gcm", "alive": True, # runtime field — should be stripped "history": [], # runtime field — should be stripped "extra": {"foo": "bar"}, # runtime field — should be stripped } BASE_YAML = """ proxies: [] proxy-providers: provider1: type: http url: https://example.com/sub interval: 3600 proxy-groups: - name: Proxy type: select use: - provider1 rules: - MATCH,DIRECT """ def test_filter_proxy_removes_runtime_fields(): filtered = filter_proxy(PROXY_FULL) assert "alive" not in filtered assert "history" not in filtered assert "extra" not in filtered assert filtered["name"] == "node1" assert filtered["server"] == "1.2.3.4" def test_expand_config_replaces_providers_with_proxies(): proxies = [ {"name": "node1", "type": "ss", "server": "1.2.3.4", "port": 443, "password": "pwd", "cipher": "aes-256-gcm"}, ] result = expand_config(BASE_YAML, {"provider1": proxies}) cfg = yaml.safe_load(result) assert "proxy-providers" not in cfg assert any(p["name"] == "node1" for p in cfg["proxies"]) def test_expand_config_fills_use_in_proxy_groups(): proxies = [ {"name": "node1", "type": "ss", "server": "1.2.3.4", "port": 443, "password": "pwd", "cipher": "aes-256-gcm"}, {"name": "node2", "type": "ss", "server": "5.6.7.8", "port": 443, "password": "pwd", "cipher": "aes-256-gcm"}, ] result = expand_config(BASE_YAML, {"provider1": proxies}) cfg = yaml.safe_load(result) proxy_group = next(g for g in cfg["proxy-groups"] if g["name"] == "Proxy") assert "use" not in proxy_group assert "node1" in proxy_group["proxies"] assert "node2" in proxy_group["proxies"] def test_expand_config_preserves_existing_proxies(): base = """ proxies: - name: static-node type: direct proxy-providers: p1: type: http url: https://example.com/sub interval: 3600 proxy-groups: [] rules: [] """ result = expand_config(base, {"p1": [{"name": "dynamic-node", "type": "ss", "server": "1.2.3.4", "port": 443}]}) cfg = yaml.safe_load(result) names = [p["name"] for p in cfg["proxies"]] assert "static-node" in names assert "dynamic-node" in names def test_expand_config_empty_providers(): result = expand_config(BASE_YAML, {}) cfg = yaml.safe_load(result) assert "proxy-providers" not in cfg def test_build_mihomo_config_merges_providers(): yaml1 = """ proxy-providers: p1: type: http url: https://a.example.com/sub interval: 3600 health-check: enable: true url: http://www.gstatic.com/generate_204 interval: 300 """ yaml2 = """ proxy-providers: p2: type: http url: https://b.example.com/sub interval: 3600 """ result = build_mihomo_config([yaml1, yaml2], "mysecret") cfg = yaml.safe_load(result) assert cfg["external-controller"] == "0.0.0.0:9090" assert cfg["secret"] == "mysecret" assert cfg["mixed-port"] == 7890 assert cfg["allow-lan"] is False assert "p1" in cfg["proxy-providers"] assert "p2" in cfg["proxy-providers"] assert cfg["proxy-providers"]["p1"]["health-check"]["enable"] is True def test_build_mihomo_config_deduplicates_by_name(): yaml1 = """ proxy-providers: p1: type: http url: https://original.example.com/sub interval: 3600 """ yaml2 = """ proxy-providers: p1: type: http url: https://duplicate.example.com/sub interval: 3600 """ result = build_mihomo_config([yaml1, yaml2], "s") cfg = yaml.safe_load(result) assert cfg["proxy-providers"]["p1"]["url"] == "https://original.example.com/sub" def test_build_mihomo_config_no_providers(): result = build_mihomo_config(["proxies: []"], "s") cfg = yaml.safe_load(result) assert "proxy-providers" not in cfg def test_expand_config_skips_proxies_without_name(): result = expand_config(BASE_YAML, {"provider1": [ {"type": "ss", "server": "1.2.3.4", "port": 443}, # no "name" {"name": "valid-node", "type": "ss", "server": "5.6.7.8", "port": 443}, ]}) cfg = yaml.safe_load(result) names = [p["name"] for p in cfg["proxies"]] assert "valid-node" in names assert len(names) == 1 def test_build_mihomo_config_skips_malformed_yaml(): valid_yaml = """ proxy-providers: p1: type: http url: https://example.com/sub interval: 3600 """ result = build_mihomo_config([valid_yaml, ":: invalid yaml ::: {{{"], "s") cfg = yaml.safe_load(result) assert "p1" in cfg["proxy-providers"] # --- inject_providers_for_delivery tests --- def test_inject_providers_returns_unchanged_when_no_subs(): base = "proxies: []\nrules:\n - MATCH,DIRECT\n" result = inject_providers_for_delivery(base, []) assert result == base def test_inject_providers_adds_entry_for_missing_sub(): base = "proxies: []\nproxy-groups: []\nrules:\n - MATCH,DIRECT\n" sub = FakeSub(name="mysub", url="https://example.com/sub") result = inject_providers_for_delivery(base, [sub]) cfg = yaml.safe_load(result) assert "mysub" in cfg["proxy-providers"] entry = cfg["proxy-providers"]["mysub"] assert entry["type"] == "http" assert entry["url"] == "https://example.com/sub" assert entry["interval"] == 3600 assert entry["health-check"]["enable"] is True def test_inject_providers_adds_use_to_fallback_and_url_test_groups(): base = ( "proxies: []\n" "proxy-groups:\n" " - name: Auto\n" " type: url-test\n" " proxies: []\n" " - name: FB\n" " type: fallback\n" " proxies: []\n" " - name: LB\n" " type: load-balance\n" " proxies: []\n" " - name: Manual\n" " type: select\n" " proxies: []\n" "rules:\n" " - MATCH,DIRECT\n" ) sub = FakeSub(name="newsub", url="https://example.com/sub") result = inject_providers_for_delivery(base, [sub]) cfg = yaml.safe_load(result) groups = {g["name"]: g for g in cfg["proxy-groups"]} assert groups["Auto"]["use"] == ["newsub"] assert groups["FB"]["use"] == ["newsub"] assert groups["LB"]["use"] == ["newsub"] assert "use" not in groups["Manual"] def test_inject_providers_does_not_add_use_to_groups_that_already_have_use(): base = ( "proxies: []\n" "proxy-groups:\n" " - name: Auto\n" " type: url-test\n" " use:\n" " - existing-provider\n" "rules:\n" " - MATCH,DIRECT\n" ) sub = FakeSub(name="newsub", url="https://example.com/sub") result = inject_providers_for_delivery(base, [sub]) cfg = yaml.safe_load(result) groups = {g["name"]: g for g in cfg["proxy-groups"]} assert groups["Auto"]["use"] == ["existing-provider"] def test_inject_providers_skips_sub_already_in_base_yaml(): base = ( "proxy-providers:\n" " existing-sub:\n" " type: http\n" " url: https://original.example.com/sub\n" " interval: 3600\n" "proxies: []\n" "proxy-groups: []\n" "rules:\n" " - MATCH,DIRECT\n" ) sub = FakeSub(name="existing-sub", url="https://different.example.com/sub") result = inject_providers_for_delivery(base, [sub]) # Should return unchanged since no new entries assert result == base