Files
ctrld/internal/rulematcher/engine_test.go
Cuong Manh Le 92f32ba16e refactor: remove unused StopOnFirstMatch field from MatchingConfig
Remove StopOnFirstMatch field that was defined but never used in the
actual matching logic.

The current implementation always evaluates all rule types and applies
a fixed precedence (Domain > MAC > Network), making the StopOnFirstMatch
field unnecessary.

Changes:
- Remove StopOnFirstMatch from MatchingConfig structs
- Update DefaultMatchingConfig() function
- Update all test cases and references
- Simplify configuration to only include Order field

This cleanup removes dead code and simplifies the configuration API
without changing any functional behavior.
2025-10-09 19:12:06 +07:00

217 lines
6.1 KiB
Go

package rulematcher
import (
"context"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/Control-D-Inc/ctrld/testhelper"
)
func TestMatchingEngine(t *testing.T) {
cfg := testhelper.SampleConfig(t)
// Convert Cidrs to IPNets like in the original test
for _, nc := range cfg.Network {
for _, cidr := range nc.Cidrs {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
t.Fatal(err)
}
nc.IPNets = append(nc.IPNets, ipNet)
}
}
tests := []struct {
name string
config *MatchingConfig
request *MatchRequest
expected *MatchingResult
}{
{
name: "Default config - network match first",
config: DefaultMatchingConfig(),
request: &MatchRequest{
SourceIP: net.ParseIP("192.168.0.1"),
SourceMac: "14:45:A0:67:83:0A",
Domain: "example.ru",
Policy: cfg.Listener["0"].Policy,
Config: cfg,
},
expected: &MatchingResult{
Upstreams: []string{"upstream.1"},
MatchedPolicy: "My Policy",
MatchedNetwork: "network.0 (unenforced)",
MatchedRule: "*.ru",
Matched: true,
SrcAddr: "192.168.0.1",
MatchedRuleType: "domain",
MatchingOrder: []RuleType{RuleTypeNetwork, RuleTypeMac, RuleTypeDomain},
},
},
{
name: "Custom order - domain first",
config: &MatchingConfig{
Order: []RuleType{RuleTypeDomain, RuleTypeNetwork, RuleTypeMac},
},
request: &MatchRequest{
SourceIP: net.ParseIP("192.168.0.1"),
SourceMac: "14:45:A0:67:83:0A",
Domain: "example.ru",
Policy: cfg.Listener["0"].Policy,
Config: cfg,
},
expected: &MatchingResult{
Upstreams: []string{"upstream.1"},
MatchedPolicy: "My Policy",
MatchedNetwork: "network.0 (unenforced)",
MatchedRule: "*.ru",
Matched: true,
SrcAddr: "192.168.0.1",
MatchedRuleType: "domain",
MatchingOrder: []RuleType{RuleTypeDomain, RuleTypeNetwork, RuleTypeMac},
},
},
{
name: "Custom order - MAC first",
config: &MatchingConfig{
Order: []RuleType{RuleTypeMac, RuleTypeNetwork, RuleTypeDomain},
},
request: &MatchRequest{
SourceIP: net.ParseIP("192.168.0.1"),
SourceMac: "14:45:A0:67:83:0A",
Domain: "example.ru",
Policy: cfg.Listener["0"].Policy,
Config: cfg,
},
expected: &MatchingResult{
Upstreams: []string{"upstream.1"},
MatchedPolicy: "My Policy",
MatchedNetwork: "network.0 (unenforced)",
MatchedRule: "*.ru",
Matched: true,
SrcAddr: "192.168.0.1",
MatchedRuleType: "domain",
MatchingOrder: []RuleType{RuleTypeMac, RuleTypeNetwork, RuleTypeDomain},
},
},
{
name: "No policy",
config: DefaultMatchingConfig(),
request: &MatchRequest{
SourceIP: net.ParseIP("192.168.0.1"),
SourceMac: "14:45:A0:67:83:0A",
Domain: "example.ru",
Policy: nil,
Config: cfg,
},
expected: &MatchingResult{
Upstreams: []string{},
MatchedPolicy: "no policy",
MatchedNetwork: "no network",
MatchedRule: "no rule",
Matched: false,
SrcAddr: "192.168.0.1",
MatchedRuleType: "",
MatchingOrder: []RuleType{RuleTypeNetwork, RuleTypeMac, RuleTypeDomain},
},
},
{
name: "No matches",
config: DefaultMatchingConfig(),
request: &MatchRequest{
SourceIP: net.ParseIP("10.0.0.1"),
SourceMac: "00:11:22:33:44:55",
Domain: "example.com",
Policy: cfg.Listener["0"].Policy,
Config: cfg,
},
expected: &MatchingResult{
Upstreams: []string{},
MatchedPolicy: "My Policy",
MatchedNetwork: "no network",
MatchedRule: "no rule",
Matched: false,
SrcAddr: "10.0.0.1",
MatchedRuleType: "",
MatchingOrder: []RuleType{RuleTypeNetwork, RuleTypeMac, RuleTypeDomain},
},
},
{
name: "MAC rule overrides network rule",
config: DefaultMatchingConfig(),
request: &MatchRequest{
SourceIP: net.ParseIP("192.168.0.1"),
SourceMac: "14:45:A0:67:83:0A",
Domain: "example.com", // This domain doesn't match any domain rules
Policy: cfg.Listener["0"].Policy,
Config: cfg,
},
expected: &MatchingResult{
Upstreams: []string{"upstream.2"},
MatchedPolicy: "My Policy",
MatchedNetwork: "14:45:a0:67:83:0a",
MatchedRule: "no rule",
Matched: true,
SrcAddr: "192.168.0.1",
MatchedRuleType: "mac",
MatchingOrder: []RuleType{RuleTypeNetwork, RuleTypeMac, RuleTypeDomain},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
engine := NewMatchingEngine(tc.config)
result := engine.FindUpstreams(context.Background(), tc.request)
assert.Equal(t, tc.expected.Upstreams, result.Upstreams)
assert.Equal(t, tc.expected.MatchedPolicy, result.MatchedPolicy)
assert.Equal(t, tc.expected.MatchedNetwork, result.MatchedNetwork)
assert.Equal(t, tc.expected.MatchedRule, result.MatchedRule)
assert.Equal(t, tc.expected.Matched, result.Matched)
assert.Equal(t, tc.expected.SrcAddr, result.SrcAddr)
assert.Equal(t, tc.expected.MatchedRuleType, result.MatchedRuleType)
assert.Equal(t, tc.expected.MatchingOrder, result.MatchingOrder)
})
}
}
func TestDefaultMatchingConfig(t *testing.T) {
config := DefaultMatchingConfig()
assert.Equal(t, []RuleType{RuleTypeNetwork, RuleTypeMac, RuleTypeDomain}, config.Order)
}
func TestMatchingEngineWithInvalidRuleType(t *testing.T) {
cfg := testhelper.SampleConfig(t)
// Convert Cidrs to IPNets like in the original test
for _, nc := range cfg.Network {
for _, cidr := range nc.Cidrs {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
t.Fatal(err)
}
nc.IPNets = append(nc.IPNets, ipNet)
}
}
config := &MatchingConfig{
Order: []RuleType{RuleType("invalid"), RuleTypeNetwork},
}
engine := NewMatchingEngine(config)
request := &MatchRequest{
SourceIP: net.ParseIP("192.168.0.1"),
Policy: cfg.Listener["0"].Policy,
Config: cfg,
}
result := engine.FindUpstreams(context.Background(), request)
// Should still work, just skip the invalid rule type
assert.True(t, result.Matched)
assert.Equal(t, "network", result.MatchedRuleType)
}