273 lines
8.5 KiB
Go
273 lines
8.5 KiB
Go
package automation
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestParseConfig_FullSchema(t *testing.T) {
|
|
raw := map[string]interface{}{
|
|
"proxy-groups": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "HK Nodes",
|
|
"type": "url-test",
|
|
"match": map[string]interface{}{
|
|
"subscriptions": []interface{}{"home-sub"},
|
|
"name-pattern": "(?i)HK|Hong Kong",
|
|
},
|
|
"url": "http://www.gstatic.com/generate_204",
|
|
"interval": 300,
|
|
},
|
|
},
|
|
"filters": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "Cheap",
|
|
"compares": []interface{}{
|
|
map[string]interface{}{
|
|
"pattern": `([\d.]+)x`,
|
|
"operator": "<=",
|
|
"value": 1.0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"patches": []interface{}{
|
|
map[string]interface{}{
|
|
"path": "dns.nameserver",
|
|
"op": "append",
|
|
"value": []interface{}{"1.1.1.1"},
|
|
},
|
|
},
|
|
}
|
|
|
|
cfg, err := ParseConfig(raw)
|
|
if err != nil {
|
|
t.Fatalf("ParseConfig() error = %v", err)
|
|
}
|
|
|
|
if len(cfg.ProxyGroups) != 1 {
|
|
t.Fatalf("expected 1 proxy-group rule, got %d", len(cfg.ProxyGroups))
|
|
}
|
|
pg := cfg.ProxyGroups[0]
|
|
if pg.Name != "HK Nodes" || pg.Type != "url-test" {
|
|
t.Fatalf("unexpected proxy-group rule: %#v", pg)
|
|
}
|
|
if len(pg.Match.Subscriptions) != 1 || pg.Match.Subscriptions[0] != "home-sub" {
|
|
t.Fatalf("unexpected match.subscriptions: %#v", pg.Match.Subscriptions)
|
|
}
|
|
if pg.Match.NamePattern != "(?i)HK|Hong Kong" {
|
|
t.Fatalf("unexpected match.name-pattern: %#v", pg.Match.NamePattern)
|
|
}
|
|
// "url" and "interval" aren't named fields on ProxyGroupRule — they must
|
|
// land in Extra via the inline tag, not get silently dropped.
|
|
if pg.Extra["url"] != "http://www.gstatic.com/generate_204" {
|
|
t.Fatalf("expected url to be captured in Extra, got %#v", pg.Extra)
|
|
}
|
|
if _, ok := pg.Extra["name"]; ok {
|
|
t.Fatalf("named field 'name' leaked into Extra: %#v", pg.Extra)
|
|
}
|
|
if _, ok := pg.Extra["match"]; ok {
|
|
t.Fatalf("named field 'match' leaked into Extra: %#v", pg.Extra)
|
|
}
|
|
|
|
if len(cfg.Filters) != 1 || len(cfg.Filters[0].Compares) != 1 ||
|
|
cfg.Filters[0].Compares[0].Operator != "<=" || cfg.Filters[0].Compares[0].Value != 1.0 {
|
|
t.Fatalf("unexpected filters: %#v", cfg.Filters)
|
|
}
|
|
|
|
if len(cfg.Patches) != 1 || cfg.Patches[0].Path != "dns.nameserver" || cfg.Patches[0].Op != "append" {
|
|
t.Fatalf("unexpected patches: %#v", cfg.Patches)
|
|
}
|
|
}
|
|
|
|
func TestParseConfig_RealWorldYAMLRoundTrip(t *testing.T) {
|
|
// Simulates the actual path: core-config.yaml is loaded via
|
|
// yamlutil.Unmarshal into map[string]interface{}, and the "ssm" key's
|
|
// raw value is what gets handed to ParseConfig.
|
|
yamlSrc := []byte(`
|
|
ssm:
|
|
proxy-groups:
|
|
- name: HK Nodes
|
|
type: select
|
|
match:
|
|
name-pattern: "HK"
|
|
patches:
|
|
- path: rules
|
|
op: prepend
|
|
value: ["DOMAIN,example.com,DIRECT"]
|
|
`)
|
|
var full map[string]interface{}
|
|
if err := yaml.Unmarshal(yamlSrc, &full); err != nil {
|
|
t.Fatalf("yaml.Unmarshal() error = %v", err)
|
|
}
|
|
|
|
cfg, err := ParseConfig(full["ssm"])
|
|
if err != nil {
|
|
t.Fatalf("ParseConfig() error = %v", err)
|
|
}
|
|
if len(cfg.ProxyGroups) != 1 || cfg.ProxyGroups[0].Name != "HK Nodes" {
|
|
t.Fatalf("unexpected proxy-groups: %#v", cfg.ProxyGroups)
|
|
}
|
|
if len(cfg.Patches) != 1 || cfg.Patches[0].Op != "prepend" {
|
|
t.Fatalf("unexpected patches: %#v", cfg.Patches)
|
|
}
|
|
}
|
|
|
|
func TestApply_EndToEnd(t *testing.T) {
|
|
proxies := []ProxyRef{
|
|
{OriginalName: "HK 01 | 1.0x", DisplayName: "sub-a | HK 01 | 1.0x", Subscription: "sub-a"},
|
|
{OriginalName: "HK 02 | 2.5x", DisplayName: "sub-a | HK 02 | 2.5x", Subscription: "sub-a"},
|
|
{OriginalName: "SG 01 | 0.5x", DisplayName: "sub-b | SG 01 | 0.5x", Subscription: "sub-b"},
|
|
}
|
|
|
|
cfg := &Config{
|
|
ProxyGroups: []ProxyGroupRule{
|
|
{Name: "HK Group", Type: "select", Match: MatchRule{NamePattern: "HK"}},
|
|
},
|
|
Filters: []FilterRule{
|
|
{Name: "Remove Expensive", Compares: []CompareRule{
|
|
{Pattern: `([\d.]+)x`, Operator: ">", Value: 1.0},
|
|
}},
|
|
},
|
|
Patches: []PatchRule{
|
|
{Path: "rules", Op: "append", Value: []interface{}{"MATCH,DIRECT"}},
|
|
},
|
|
}
|
|
|
|
// Simulates a proxy-group already merged in from a subscription, which
|
|
// filters must also scrub even though proxy-groups rules never touch it.
|
|
finalConfig := map[string]interface{}{
|
|
"rules": []interface{}{},
|
|
"proxy-groups": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "sub-a | Auto",
|
|
"type": "url-test",
|
|
"proxies": []interface{}{
|
|
"sub-a | HK 01 | 1.0x", "sub-a | HK 02 | 2.5x",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
errs := Apply(finalConfig, cfg, proxies)
|
|
if len(errs) != 0 {
|
|
t.Fatalf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
groups, ok := finalConfig["proxy-groups"].([]interface{})
|
|
if !ok || len(groups) != 2 {
|
|
t.Fatalf("expected 2 proxy-groups, got %#v", finalConfig["proxy-groups"])
|
|
}
|
|
|
|
subGroup := groups[0].(map[string]interface{})
|
|
if subGroup["name"] != "sub-a | Auto" {
|
|
t.Fatalf("unexpected first group: %#v", subGroup)
|
|
}
|
|
subProxies := subGroup["proxies"].([]interface{})
|
|
if len(subProxies) != 1 || subProxies[0] != "sub-a | HK 01 | 1.0x" {
|
|
t.Fatalf("expected the expensive proxy scrubbed from the subscription group, got %#v", subProxies)
|
|
}
|
|
|
|
hkGroup := groups[1].(map[string]interface{})
|
|
if hkGroup["name"] != "HK Group" {
|
|
t.Fatalf("unexpected second group: %#v", hkGroup)
|
|
}
|
|
hkProxies := hkGroup["proxies"].([]interface{})
|
|
if len(hkProxies) != 1 || hkProxies[0] != "sub-a | HK 01 | 1.0x" {
|
|
t.Fatalf("expected the expensive proxy scrubbed from the built HK Group too, got %#v", hkProxies)
|
|
}
|
|
|
|
if rules, _ := finalConfig["rules"].([]interface{}); len(rules) != 1 || rules[0] != "MATCH,DIRECT" {
|
|
t.Fatalf("unexpected rules after patch: %#v", finalConfig["rules"])
|
|
}
|
|
}
|
|
|
|
func TestApply_InvalidFilterDoesNotBlockOthers(t *testing.T) {
|
|
proxies := []ProxyRef{
|
|
{OriginalName: "HK 01", DisplayName: "sub-a | HK 01", Subscription: "sub-a"},
|
|
}
|
|
cfg := &Config{
|
|
ProxyGroups: []ProxyGroupRule{
|
|
{Name: "All", Type: "select"},
|
|
},
|
|
Filters: []FilterRule{
|
|
{Name: "Bad", Compares: []CompareRule{{Pattern: "(", Operator: "<="}}},
|
|
},
|
|
}
|
|
finalConfig := map[string]interface{}{}
|
|
errs := Apply(finalConfig, cfg, proxies)
|
|
if len(errs) != 1 {
|
|
t.Fatalf("expected exactly 1 error for the bad regexp, got %v", errs)
|
|
}
|
|
|
|
groups, ok := finalConfig["proxy-groups"].([]interface{})
|
|
if !ok || len(groups) != 1 {
|
|
t.Fatalf("expected the valid proxy-groups rule to still run: %#v", finalConfig["proxy-groups"])
|
|
}
|
|
}
|
|
|
|
func TestApplyFilters_NameOnlyActsAsDenylist(t *testing.T) {
|
|
proxies := []ProxyRef{
|
|
{OriginalName: "HK 01", DisplayName: "sub-a | HK 01", Subscription: "sub-a"},
|
|
{OriginalName: "SG 01", DisplayName: "sub-b | SG 01", Subscription: "sub-b"},
|
|
}
|
|
finalConfig := map[string]interface{}{
|
|
"proxy-groups": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "Auto",
|
|
"proxies": []interface{}{"sub-a | HK 01", "sub-b | SG 01", "DIRECT"},
|
|
},
|
|
},
|
|
}
|
|
|
|
errs := applyFilters(finalConfig, []FilterRule{
|
|
{Name: "No HK", Match: MatchRule{NamePattern: "^HK"}},
|
|
}, proxies)
|
|
if len(errs) != 0 {
|
|
t.Fatalf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
names := finalConfig["proxy-groups"].([]interface{})[0].(map[string]interface{})["proxies"].([]interface{})
|
|
want := []interface{}{"sub-b | SG 01", "DIRECT"}
|
|
if len(names) != 2 || names[0] != want[0] || names[1] != want[1] {
|
|
t.Fatalf("expected HK proxy removed, DIRECT and SG left alone, got %#v", names)
|
|
}
|
|
}
|
|
|
|
func TestApplyFilters_MultipleComparesAreANDed(t *testing.T) {
|
|
proxies := []ProxyRef{
|
|
{OriginalName: "HK 01 | 1.0x | 50ms", DisplayName: "sub-a | HK 01 | 1.0x | 50ms", Subscription: "sub-a"},
|
|
{OriginalName: "HK 02 | 1.0x | 200ms", DisplayName: "sub-a | HK 02 | 1.0x | 200ms", Subscription: "sub-a"},
|
|
{OriginalName: "SG 01 | 2.5x | 50ms", DisplayName: "sub-b | SG 01 | 2.5x | 50ms", Subscription: "sub-b"},
|
|
}
|
|
finalConfig := map[string]interface{}{
|
|
"proxy-groups": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "Auto",
|
|
"proxies": []interface{}{
|
|
"sub-a | HK 01 | 1.0x | 50ms",
|
|
"sub-a | HK 02 | 1.0x | 200ms",
|
|
"sub-b | SG 01 | 2.5x | 50ms",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
errs := applyFilters(finalConfig, []FilterRule{
|
|
{Name: "Remove Cheap and Fast", Compares: []CompareRule{
|
|
{Pattern: `([\d.]+)x`, Operator: "<=", Value: 1.0},
|
|
{Pattern: `(\d+)ms`, Operator: "<=", Value: 100},
|
|
}},
|
|
}, proxies)
|
|
if len(errs) != 0 {
|
|
t.Fatalf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
names := finalConfig["proxy-groups"].([]interface{})[0].(map[string]interface{})["proxies"].([]interface{})
|
|
want := []interface{}{"sub-a | HK 02 | 1.0x | 200ms", "sub-b | SG 01 | 2.5x | 50ms"}
|
|
if len(names) != 2 || names[0] != want[0] || names[1] != want[1] {
|
|
t.Fatalf("expected only the cheap+fast proxy removed (both conditions must hold), got %#v", names)
|
|
}
|
|
}
|