headscale/hscontrol/policy/v2/utils_test.go
Kristoffer Dalby 25fdad3949 hscontrol/policy/v2: define sentinel errors
Add comprehensive sentinel errors for all error conditions in the policy
engine and use consistent error wrapping patterns with fmt.Errorf("%w: ...).
Update test expectations to match the new error message formats.
2026-01-21 15:54:38 +00:00

98 lines
3.1 KiB
Go

package v2
import (
"errors"
"testing"
"github.com/google/go-cmp/cmp"
"tailscale.com/tailcfg"
)
// TestParseDestinationAndPort tests the parseDestinationAndPort function using table-driven tests.
func TestParseDestinationAndPort(t *testing.T) {
testCases := []struct {
input string
expectedDst string
expectedPort string
expectedErr error
}{
{"git-server:*", "git-server", "*", nil},
{"192.168.1.0/24:22", "192.168.1.0/24", "22", nil},
{"fd7a:115c:a1e0::2:22", "fd7a:115c:a1e0::2", "22", nil},
{"fd7a:115c:a1e0::2/128:22", "fd7a:115c:a1e0::2/128", "22", nil},
{"tag:montreal-webserver:80,443", "tag:montreal-webserver", "80,443", nil},
{"tag:api-server:443", "tag:api-server", "443", nil},
{"example-host-1:*", "example-host-1", "*", nil},
{"hostname:80-90", "hostname", "80-90", nil},
{"invalidinput", "", "", ErrInputMissingColon},
{":invalid", "", "", ErrInputStartsWithColon},
{"invalid:", "", "", ErrInputEndsWithColon},
}
for _, testCase := range testCases {
dst, port, err := splitDestinationAndPort(testCase.input)
if dst != testCase.expectedDst || port != testCase.expectedPort || !errors.Is(err, testCase.expectedErr) {
t.Errorf("parseDestinationAndPort(%q) = (%q, %q, %v), want (%q, %q, %v)",
testCase.input, dst, port, err, testCase.expectedDst, testCase.expectedPort, testCase.expectedErr)
}
}
}
func TestParsePort(t *testing.T) {
tests := []struct {
input string
expected uint16
err error
}{
{"80", 80, nil},
{"0", 0, nil},
{"65535", 65535, nil},
{"-1", 0, ErrPortOutOfRange},
{"65536", 0, ErrPortOutOfRange},
{"abc", 0, ErrInvalidPortNumber},
{"", 0, ErrInvalidPortNumber},
}
for _, test := range tests {
result, err := parsePort(test.input)
if !errors.Is(err, test.err) {
t.Errorf("parsePort(%q) error = %v, expected error = %v", test.input, err, test.err)
}
if result != test.expected {
t.Errorf("parsePort(%q) = %v, expected %v", test.input, result, test.expected)
}
}
}
func TestParsePortRange(t *testing.T) {
tests := []struct {
input string
expected []tailcfg.PortRange
err error
}{
{"80", []tailcfg.PortRange{{First: 80, Last: 80}}, nil},
{"80-90", []tailcfg.PortRange{{First: 80, Last: 90}}, nil},
{"80,90", []tailcfg.PortRange{{First: 80, Last: 80}, {First: 90, Last: 90}}, nil},
{"80-91,92,93-95", []tailcfg.PortRange{{First: 80, Last: 91}, {First: 92, Last: 92}, {First: 93, Last: 95}}, nil},
{"*", []tailcfg.PortRange{tailcfg.PortRangeAny}, nil},
{"80-", nil, ErrInvalidPortRange},
{"-90", nil, ErrInvalidPortRange},
{"80-90,", nil, ErrInvalidPortNumber},
{"80,90-", nil, ErrInvalidPortRange},
{"80-90,abc", nil, ErrInvalidPortNumber},
{"80-90,65536", nil, ErrPortOutOfRange},
{"80-90,90-80", nil, ErrPortRangeInverted},
}
for _, test := range tests {
result, err := parsePortRange(test.input)
if !errors.Is(err, test.err) {
t.Errorf("parsePortRange(%q) error = %v, expected error = %v", test.input, err, test.err)
}
if diff := cmp.Diff(result, test.expected); diff != "" {
t.Errorf("parsePortRange(%q) mismatch (-want +got):\n%s", test.input, diff)
}
}
}