wireguard-ui/util/util_test.go

886 lines
25 KiB
Go

package util
import (
"net"
"os"
"testing"
"github.com/labstack/gommon/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/DigitalTolk/wireguard-ui/model"
)
func TestValidateCIDR(t *testing.T) {
assert.True(t, ValidateCIDR("10.0.0.0/24"))
assert.True(t, ValidateCIDR("192.168.1.0/32"))
assert.True(t, ValidateCIDR("fd00::/64"))
assert.False(t, ValidateCIDR("10.0.0.0"))
assert.False(t, ValidateCIDR("invalid"))
assert.False(t, ValidateCIDR(""))
}
func TestValidateCIDRList(t *testing.T) {
assert.True(t, ValidateCIDRList([]string{"10.0.0.0/24", "192.168.0.0/16"}, false))
assert.False(t, ValidateCIDRList([]string{"10.0.0.0/24", "invalid"}, false))
assert.True(t, ValidateCIDRList([]string{"10.0.0.0/24", ""}, true))
assert.False(t, ValidateCIDRList([]string{"10.0.0.0/24", ""}, false))
assert.True(t, ValidateCIDRList([]string{}, false))
}
func TestValidateAllowedIPs(t *testing.T) {
assert.True(t, ValidateAllowedIPs([]string{"0.0.0.0/0"}))
assert.True(t, ValidateAllowedIPs([]string{"10.0.0.0/24", "192.168.1.0/24"}))
assert.False(t, ValidateAllowedIPs([]string{"not-a-cidr"}))
}
func TestValidateExtraAllowedIPs(t *testing.T) {
assert.True(t, ValidateExtraAllowedIPs([]string{"10.0.0.0/24"}))
assert.True(t, ValidateExtraAllowedIPs([]string{"10.0.0.0/24", ""}))
assert.False(t, ValidateExtraAllowedIPs([]string{"invalid"}))
}
func TestValidateServerAddresses(t *testing.T) {
assert.True(t, ValidateServerAddresses([]string{"10.252.1.0/24"}))
assert.False(t, ValidateServerAddresses([]string{"10.252.1.0"}))
}
func TestValidateIPAddress(t *testing.T) {
assert.True(t, ValidateIPAddress("10.0.0.1"))
assert.True(t, ValidateIPAddress("::1"))
assert.True(t, ValidateIPAddress("192.168.1.1"))
assert.False(t, ValidateIPAddress("999.0.0.1"))
assert.False(t, ValidateIPAddress("invalid"))
assert.False(t, ValidateIPAddress(""))
}
func TestValidateIPAddressList(t *testing.T) {
assert.True(t, ValidateIPAddressList([]string{"1.1.1.1", "8.8.8.8"}))
assert.False(t, ValidateIPAddressList([]string{"1.1.1.1", "bad"}))
assert.True(t, ValidateIPAddressList([]string{}))
}
func TestGetIPFromCIDR(t *testing.T) {
ip, err := GetIPFromCIDR("10.252.1.5/24")
assert.NoError(t, err)
assert.Equal(t, "10.252.1.5", ip)
ip, err = GetIPFromCIDR("192.168.1.100/32")
assert.NoError(t, err)
assert.Equal(t, "192.168.1.100", ip)
_, err = GetIPFromCIDR("invalid")
assert.Error(t, err)
}
func TestContainsCIDR(t *testing.T) {
_, net1, _ := net.ParseCIDR("10.0.0.0/8")
_, net2, _ := net.ParseCIDR("10.0.0.0/24")
_, net3, _ := net.ParseCIDR("192.168.0.0/24")
assert.True(t, ContainsCIDR(net1, net2))
assert.False(t, ContainsCIDR(net2, net1))
assert.False(t, ContainsCIDR(net1, net3))
}
func TestGetBroadcastIP(t *testing.T) {
_, netAddr, _ := net.ParseCIDR("10.0.0.0/24")
broadcast := GetBroadcastIP(netAddr)
assert.Equal(t, "10.0.0.255", broadcast.String())
_, netAddr2, _ := net.ParseCIDR("192.168.1.0/30")
broadcast2 := GetBroadcastIP(netAddr2)
assert.Equal(t, "192.168.1.3", broadcast2.String())
}
func TestGetBroadcastAndNetworkAddrsLookup(t *testing.T) {
lookup := GetBroadcastAndNetworkAddrsLookup([]string{"10.0.0.0/24"})
assert.True(t, lookup["10.0.0.0"])
assert.True(t, lookup["10.0.0.255"])
assert.False(t, lookup["10.0.0.1"])
}
func TestGetAvailableIP(t *testing.T) {
ip, err := GetAvailableIP("10.0.0.0/24", []string{}, []string{"10.0.0.0/24"})
assert.NoError(t, err)
assert.Equal(t, "10.0.0.1", ip)
ip, err = GetAvailableIP("10.0.0.0/24", []string{"10.0.0.1"}, []string{"10.0.0.0/24"})
assert.NoError(t, err)
assert.Equal(t, "10.0.0.2", ip)
_, err = GetAvailableIP("invalid", []string{}, []string{})
assert.Error(t, err)
// exhaust a /30 (only 2 usable IPs: .1 and .2)
_, err = GetAvailableIP("10.0.0.0/30", []string{"10.0.0.1", "10.0.0.2"}, []string{"10.0.0.0/30"})
assert.Error(t, err)
}
func TestValidateIPAllocation(t *testing.T) {
serverAddrs := []string{"10.252.1.0/24"}
ok, err := ValidateIPAllocation(serverAddrs, []string{}, []string{"10.252.1.5/32"})
assert.True(t, ok)
assert.NoError(t, err)
// already allocated
ok, err = ValidateIPAllocation(serverAddrs, []string{"10.252.1.5"}, []string{"10.252.1.5/32"})
assert.False(t, ok)
assert.Error(t, err)
// not in server network
ok, err = ValidateIPAllocation(serverAddrs, []string{}, []string{"192.168.1.5/32"})
assert.False(t, ok)
assert.Error(t, err)
// invalid CIDR
ok, err = ValidateIPAllocation(serverAddrs, []string{}, []string{"not-cidr"})
assert.False(t, ok)
assert.Error(t, err)
}
func TestBuildClientConfig(t *testing.T) {
client := model.Client{
AllocatedIPs: []string{"10.252.1.2/32"},
PrivateKey: "clientprivkey",
AllowedIPs: []string{"0.0.0.0/0"},
UseServerDNS: true,
PresharedKey: "psk123",
}
server := model.Server{
KeyPair: &model.ServerKeypair{
PublicKey: "serverpubkey",
},
Interface: &model.ServerInterface{
ListenPort: 51820,
},
}
setting := model.GlobalSetting{
EndpointAddress: "vpn.example.com",
DNSServers: []string{"1.1.1.1"},
MTU: 1450,
PersistentKeepalive: 15,
}
config := BuildClientConfig(client, server, setting)
assert.Contains(t, config, "# CONFIDENTIAL")
assert.Contains(t, config, "# This file contains private cryptographic keys.")
assert.Contains(t, config, "[Interface]")
assert.Contains(t, config, "Address = 10.252.1.2/32")
assert.Contains(t, config, "PrivateKey = clientprivkey")
assert.Contains(t, config, "DNS = 1.1.1.1")
assert.Contains(t, config, "MTU = 1450")
assert.Contains(t, config, "[Peer]")
assert.Contains(t, config, "PublicKey = serverpubkey")
assert.Contains(t, config, "PresharedKey = psk123")
assert.Contains(t, config, "AllowedIPs = 0.0.0.0/0")
assert.Contains(t, config, "Endpoint = vpn.example.com:51820")
assert.Contains(t, config, "PersistentKeepalive = 15")
}
func TestBuildClientConfig_NoDNS(t *testing.T) {
client := model.Client{
AllocatedIPs: []string{"10.0.0.2/32"},
PrivateKey: "key",
AllowedIPs: []string{"0.0.0.0/0"},
UseServerDNS: false,
}
server := model.Server{
KeyPair: &model.ServerKeypair{PublicKey: "pub"},
Interface: &model.ServerInterface{ListenPort: 51820},
}
setting := model.GlobalSetting{
EndpointAddress: "1.2.3.4",
DNSServers: []string{"1.1.1.1"},
}
config := BuildClientConfig(client, server, setting)
assert.NotContains(t, config, "DNS =")
assert.NotContains(t, config, "MTU =")
assert.NotContains(t, config, "PresharedKey =")
assert.NotContains(t, config, "PersistentKeepalive =")
}
func TestBuildClientConfig_EndpointWithPort(t *testing.T) {
client := model.Client{
AllocatedIPs: []string{"10.0.0.2/32"},
PrivateKey: "key",
AllowedIPs: []string{"0.0.0.0/0"},
}
server := model.Server{
KeyPair: &model.ServerKeypair{PublicKey: "pub"},
Interface: &model.ServerInterface{ListenPort: 51820},
}
setting := model.GlobalSetting{
EndpointAddress: "vpn.example.com:9999",
}
config := BuildClientConfig(client, server, setting)
assert.Contains(t, config, "Endpoint = vpn.example.com:9999")
}
func TestLookupEnvOrString(t *testing.T) {
assert.Equal(t, "default", LookupEnvOrString("NONEXISTENT_ENV_VAR_12345", "default"))
os.Setenv("TEST_LOOKUP_STR", "custom")
defer os.Unsetenv("TEST_LOOKUP_STR")
assert.Equal(t, "custom", LookupEnvOrString("TEST_LOOKUP_STR", "default"))
}
func TestLookupEnvOrBool(t *testing.T) {
assert.True(t, LookupEnvOrBool("NONEXISTENT_ENV_VAR_12345", true))
assert.False(t, LookupEnvOrBool("NONEXISTENT_ENV_VAR_12345", false))
os.Setenv("TEST_LOOKUP_BOOL", "true")
defer os.Unsetenv("TEST_LOOKUP_BOOL")
assert.True(t, LookupEnvOrBool("TEST_LOOKUP_BOOL", false))
}
func TestLookupEnvOrInt(t *testing.T) {
assert.Equal(t, 42, LookupEnvOrInt("NONEXISTENT_ENV_VAR_12345", 42))
os.Setenv("TEST_LOOKUP_INT", "99")
defer os.Unsetenv("TEST_LOOKUP_INT")
assert.Equal(t, 99, LookupEnvOrInt("TEST_LOOKUP_INT", 42))
}
func TestLookupEnvOrStrings(t *testing.T) {
assert.Equal(t, []string{"a", "b"}, LookupEnvOrStrings("NONEXISTENT_ENV_VAR_12345", []string{"a", "b"}))
os.Setenv("TEST_LOOKUP_STRS", "x,y,z")
defer os.Unsetenv("TEST_LOOKUP_STRS")
assert.Equal(t, []string{"x", "y", "z"}, LookupEnvOrStrings("TEST_LOOKUP_STRS", []string{}))
}
func TestLookupEnvOrFile(t *testing.T) {
assert.Equal(t, "default", LookupEnvOrFile("NONEXISTENT_ENV_VAR_12345", "default"))
tmpFile, err := os.CreateTemp("", "test_lookup")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())
tmpFile.WriteString("secret_from_file")
tmpFile.Close()
os.Setenv("TEST_LOOKUP_FILE", tmpFile.Name())
defer os.Unsetenv("TEST_LOOKUP_FILE")
assert.Equal(t, "secret_from_file", LookupEnvOrFile("TEST_LOOKUP_FILE", "default"))
}
func TestParseLogLevel(t *testing.T) {
lvl, err := ParseLogLevel("debug")
assert.NoError(t, err)
assert.Equal(t, log.DEBUG, lvl)
lvl, err = ParseLogLevel("INFO")
assert.NoError(t, err)
assert.Equal(t, log.INFO, lvl)
lvl, err = ParseLogLevel("warn")
assert.NoError(t, err)
assert.Equal(t, log.WARN, lvl)
lvl, err = ParseLogLevel("error")
assert.NoError(t, err)
assert.Equal(t, log.ERROR, lvl)
lvl, err = ParseLogLevel("off")
assert.NoError(t, err)
assert.Equal(t, log.OFF, lvl)
_, err = ParseLogLevel("invalid")
assert.Error(t, err)
}
func TestRandomString(t *testing.T) {
s := RandomString(32)
assert.Len(t, s, 32)
s2 := RandomString(32)
assert.Len(t, s2, 32)
// extremely unlikely to be equal
assert.NotEqual(t, s, s2)
assert.Len(t, RandomString(0), 0)
}
func TestGetCookiePath(t *testing.T) {
original := BasePath
defer func() { BasePath = original }()
BasePath = ""
assert.Equal(t, "/", GetCookiePath())
BasePath = "/app"
assert.Equal(t, "/app", GetCookiePath())
}
func TestGetDBUserCRC32(t *testing.T) {
user1 := model.User{Username: "admin", Admin: true}
user2 := model.User{Username: "admin", Admin: true}
user3 := model.User{Username: "other", Admin: false}
hash1 := GetDBUserCRC32(user1)
hash2 := GetDBUserCRC32(user2)
hash3 := GetDBUserCRC32(user3)
assert.Equal(t, hash1, hash2)
assert.NotEqual(t, hash1, hash3)
}
func TestConcatMultipleSlices(t *testing.T) {
result := ConcatMultipleSlices([]byte{1, 2}, []byte{3, 4}, []byte{5})
assert.Equal(t, []byte{1, 2, 3, 4, 5}, result)
result = ConcatMultipleSlices()
assert.Equal(t, []byte{}, result)
result = ConcatMultipleSlices([]byte{1})
assert.Equal(t, []byte{1}, result)
}
func TestFillClientSubnetRange(t *testing.T) {
// reset global state
SubnetRanges = map[string][]*net.IPNet{}
SubnetRangesOrder = nil
IPToSubnetRange = map[string]uint16{}
SubnetRanges = ParseSubnetRanges("LAN:10.0.0.0/8")
client := model.ClientData{
Client: &model.Client{
AllocatedIPs: []string{"10.0.0.5/32"},
},
}
result := FillClientSubnetRange(client)
assert.Contains(t, result.Client.SubnetRanges, "LAN")
}
func TestGetSubnetRangesString(t *testing.T) {
SubnetRanges = map[string][]*net.IPNet{}
SubnetRangesOrder = nil
assert.Equal(t, "", GetSubnetRangesString())
SubnetRanges = ParseSubnetRanges("LAN:10.0.0.0/24")
result := GetSubnetRangesString()
assert.Contains(t, result, "LAN:")
assert.Contains(t, result, "10.0.0.0/24")
}
func TestGetInterfaceIPs(t *testing.T) {
ips, err := GetInterfaceIPs()
assert.NoError(t, err)
// should return at least an empty list, not error
assert.NotNil(t, ips)
}
func TestManagePerms(t *testing.T) {
tmpFile, err := os.CreateTemp("", "test_perms")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())
tmpFile.Close()
err = ManagePerms(tmpFile.Name())
assert.NoError(t, err)
err = ManagePerms("/nonexistent/path")
assert.Error(t, err)
}
func TestWriteWireGuardServerConfig(t *testing.T) {
tmpDir := t.TempDir()
confPath := tmpDir + "/wg0.conf"
serverConfig := model.Server{
Interface: &model.ServerInterface{
Addresses: []string{"10.0.0.1/24"},
ListenPort: 51820,
PostUp: "iptables -A",
PostDown: "iptables -D",
},
KeyPair: &model.ServerKeypair{
PrivateKey: "serverprivkey",
PublicKey: "serverpubkey",
},
}
clientDataList := []model.ClientData{
{
Client: &model.Client{
ID: "client1",
Name: "Test Client",
Email: "test@example.com",
PublicKey: "clientpubkey",
PresharedKey: "clientpsk",
AllocatedIPs: []string{"10.0.0.2/32"},
ExtraAllowedIPs: []string{},
Enabled: true,
AdditionalNotes: "line1\nline2",
},
},
{
Client: &model.Client{
ID: "client2",
Name: "Disabled",
PublicKey: "pub2",
AllocatedIPs: []string{"10.0.0.3/32"},
Enabled: false,
},
},
}
globalSettings := model.GlobalSetting{
MTU: 1420,
PersistentKeepalive: 25,
Table: "auto",
ConfigFilePath: confPath,
}
// Use an in-memory FS with the wg.conf template
tmplFS := os.DirFS("../templates")
err := WriteWireGuardServerConfig(tmplFS, serverConfig, clientDataList, nil, globalSettings)
require.NoError(t, err)
content, err := os.ReadFile(confPath)
require.NoError(t, err)
s := string(content)
assert.Contains(t, s, "[Interface]")
assert.Contains(t, s, "PrivateKey = serverprivkey")
assert.Contains(t, s, "ListenPort = 51820")
assert.Contains(t, s, "PublicKey = clientpubkey")
// multiline notes should be escaped
assert.Contains(t, s, "# line2")
// disabled client should NOT appear as a [Peer]
assert.NotContains(t, s, "pub2")
}
func TestWriteWireGuardServerConfig_CustomTemplate(t *testing.T) {
tmpDir := t.TempDir()
confPath := tmpDir + "/wg0.conf"
tmplPath := tmpDir + "/custom.conf"
// write a custom template
customTmpl := `[Interface]
PrivateKey = {{ .serverConfig.KeyPair.PrivateKey }}
`
err := os.WriteFile(tmplPath, []byte(customTmpl), 0644)
require.NoError(t, err)
// set WgConfTemplate
original := WgConfTemplate
WgConfTemplate = tmplPath
defer func() { WgConfTemplate = original }()
serverConfig := model.Server{
Interface: &model.ServerInterface{ListenPort: 51820},
KeyPair: &model.ServerKeypair{PrivateKey: "customprivkey", PublicKey: "pub"},
}
globalSettings := model.GlobalSetting{ConfigFilePath: confPath}
err = WriteWireGuardServerConfig(nil, serverConfig, nil, nil, globalSettings)
require.NoError(t, err)
content, err := os.ReadFile(confPath)
require.NoError(t, err)
assert.Contains(t, string(content), "customprivkey")
}
func TestWriteWireGuardServerConfig_InvalidPath(t *testing.T) {
serverConfig := model.Server{
Interface: &model.ServerInterface{ListenPort: 51820},
KeyPair: &model.ServerKeypair{PrivateKey: "priv", PublicKey: "pub"},
}
globalSettings := model.GlobalSetting{ConfigFilePath: "/nonexistent/dir/wg0.conf"}
tmplFS := os.DirFS("../templates")
err := WriteWireGuardServerConfig(tmplFS, serverConfig, nil, nil, globalSettings)
assert.Error(t, err)
}
func TestStringFromEmbedFile(t *testing.T) {
tmplFS := os.DirFS("../templates")
content, err := StringFromEmbedFile(tmplFS, "wg.conf")
require.NoError(t, err)
assert.Contains(t, content, "[Interface]")
_, err = StringFromEmbedFile(tmplFS, "nonexistent.conf")
assert.Error(t, err)
}
func TestClientDefaultsFromEnv(t *testing.T) {
// test defaults
os.Unsetenv(DefaultClientAllowedIpsEnvVar)
os.Unsetenv(DefaultClientExtraAllowedIpsEnvVar)
os.Unsetenv(DefaultClientUseServerDNSEnvVar)
defaults := ClientDefaultsFromEnv()
assert.Equal(t, []string{"0.0.0.0/0"}, defaults.AllowedIps)
assert.Equal(t, []string{}, defaults.ExtraAllowedIps)
assert.True(t, defaults.UseServerDNS)
// test with env overrides
os.Setenv(DefaultClientAllowedIpsEnvVar, "10.0.0.0/8,192.168.0.0/16")
os.Setenv(DefaultClientUseServerDNSEnvVar, "false")
defer os.Unsetenv(DefaultClientAllowedIpsEnvVar)
defer os.Unsetenv(DefaultClientUseServerDNSEnvVar)
defaults = ClientDefaultsFromEnv()
assert.Equal(t, []string{"10.0.0.0/8", "192.168.0.0/16"}, defaults.AllowedIps)
assert.False(t, defaults.UseServerDNS)
}
func TestGetCurrentHash_WithMockStore(t *testing.T) {
store := newMockStore()
clientHash, serverHash := GetCurrentHash(store)
assert.NotEmpty(t, clientHash)
assert.NotEmpty(t, serverHash)
assert.NotEqual(t, "error", clientHash)
assert.NotEqual(t, "error", serverHash)
}
func TestHashesChanged_WithMockStore(t *testing.T) {
store := newMockStore()
// Initially hashes are "none" which differs from computed
changed := HashesChanged(store)
assert.True(t, changed)
// After updating, they should match
err := UpdateHashes(store)
require.NoError(t, err)
changed = HashesChanged(store)
assert.False(t, changed)
}
func TestUpdateHashes_WithMockStore(t *testing.T) {
store := newMockStore()
err := UpdateHashes(store)
require.NoError(t, err)
hashes, err := store.GetHashes()
require.NoError(t, err)
assert.NotEqual(t, "none", hashes.Client)
assert.NotEqual(t, "none", hashes.Server)
}
func TestValidateAndFixSubnetRanges_WithMockStore(t *testing.T) {
store := newMockStore()
// Server has 10.0.0.0/24
SubnetRanges = map[string][]*net.IPNet{}
SubnetRangesOrder = nil
IPToSubnetRange = map[string]uint16{}
SubnetRanges = ParseSubnetRanges("valid:10.0.0.0/26;invalid:192.168.99.0/24")
err := ValidateAndFixSubnetRanges(store)
require.NoError(t, err)
// valid range should remain
assert.NotNil(t, SubnetRanges["valid"])
// invalid range should be removed
_, hasInvalid := SubnetRanges["invalid"]
assert.False(t, hasInvalid)
}
func TestValidateAndFixSubnetRanges_AllInvalid(t *testing.T) {
store := newMockStore()
// Server has 10.0.0.0/24
SubnetRanges = map[string][]*net.IPNet{}
SubnetRangesOrder = nil
IPToSubnetRange = map[string]uint16{}
SubnetRanges = ParseSubnetRanges("outside:192.168.0.0/16")
err := ValidateAndFixSubnetRanges(store)
require.NoError(t, err)
_, hasOutside := SubnetRanges["outside"]
assert.False(t, hasOutside)
}
func TestValidateAndFixSubnetRanges_EmptyRanges(t *testing.T) {
store := newMockStore()
SubnetRangesOrder = nil
SubnetRanges = map[string][]*net.IPNet{}
err := ValidateAndFixSubnetRanges(store)
require.NoError(t, err)
}
func TestGetBroadcastIP_IPv6(t *testing.T) {
_, netAddr, _ := net.ParseCIDR("fd00::/120")
broadcast := GetBroadcastIP(netAddr)
assert.Equal(t, "fd00::ff", broadcast.String())
}
func TestGetBroadcastAndNetworkAddrsLookup_Invalid(t *testing.T) {
lookup := GetBroadcastAndNetworkAddrsLookup([]string{"invalid"})
assert.Empty(t, lookup)
}
func TestGetBroadcastAndNetworkAddrsLookup_Multiple(t *testing.T) {
lookup := GetBroadcastAndNetworkAddrsLookup([]string{"10.0.0.0/24", "192.168.1.0/30"})
assert.True(t, lookup["10.0.0.0"])
assert.True(t, lookup["10.0.0.255"])
assert.True(t, lookup["192.168.1.0"])
assert.True(t, lookup["192.168.1.3"])
}
func TestBuildClientConfig_EndpointInvalidPort(t *testing.T) {
client := model.Client{
AllocatedIPs: []string{"10.0.0.2/32"},
PrivateKey: "key",
AllowedIPs: []string{"0.0.0.0/0"},
}
server := model.Server{
KeyPair: &model.ServerKeypair{PublicKey: "pub"},
Interface: &model.ServerInterface{ListenPort: 51820},
}
setting := model.GlobalSetting{
EndpointAddress: "vpn.example.com:notanumber",
}
config := BuildClientConfig(client, server, setting)
// should fall back to server listen port
assert.Contains(t, config, "Endpoint = vpn.example.com:51820")
}
func TestLookupEnvOrBool_InvalidValue(t *testing.T) {
os.Setenv("TEST_BOOL_INVALID", "notabool")
defer os.Unsetenv("TEST_BOOL_INVALID")
// should return default-ish false since ParseBool fails
result := LookupEnvOrBool("TEST_BOOL_INVALID", true)
assert.False(t, result) // ParseBool returns false on error
}
func TestLookupEnvOrInt_InvalidValue(t *testing.T) {
os.Setenv("TEST_INT_INVALID", "notanint")
defer os.Unsetenv("TEST_INT_INVALID")
result := LookupEnvOrInt("TEST_INT_INVALID", 42)
assert.Equal(t, 0, result) // Atoi returns 0 on error
}
func TestLookupEnvOrFile_InvalidFilePath(t *testing.T) {
os.Setenv("TEST_FILE_INVALID", "/nonexistent/file/path")
defer os.Unsetenv("TEST_FILE_INVALID")
result := LookupEnvOrFile("TEST_FILE_INVALID", "default")
assert.Equal(t, "default", result) // file open fails, returns default
}
func TestFindSubnetRangeForIP(t *testing.T) {
// reset global state
SubnetRanges = map[string][]*net.IPNet{}
SubnetRangesOrder = nil
IPToSubnetRange = map[string]uint16{}
SubnetRanges = ParseSubnetRanges("LAN:10.0.0.0/24;WAN:192.168.0.0/16")
// test finding a matching subnet range
client := model.ClientData{
Client: &model.Client{
AllocatedIPs: []string{"10.0.0.5/32"},
},
}
result := FillClientSubnetRange(client)
assert.Contains(t, result.Client.SubnetRanges, "LAN")
// test with IP not in any range
client2 := model.ClientData{
Client: &model.Client{
AllocatedIPs: []string{"172.16.0.1/32"},
},
}
result2 := FillClientSubnetRange(client2)
assert.Empty(t, result2.Client.SubnetRanges)
// test invalid CIDR
client3 := model.ClientData{
Client: &model.Client{
AllocatedIPs: []string{"not-a-cidr"},
},
}
result3 := FillClientSubnetRange(client3)
assert.Empty(t, result3.Client.SubnetRanges)
// test cached lookup (call again with same IP)
client4 := model.ClientData{
Client: &model.Client{
AllocatedIPs: []string{"10.0.0.5/32"},
},
}
result4 := FillClientSubnetRange(client4)
assert.Contains(t, result4.Client.SubnetRanges, "LAN")
}
// --- WriteWireGuardServerConfig with custom template (additional) ---
func TestWriteWireGuardServerConfig_CustomTemplateFile(t *testing.T) {
tmpDir := t.TempDir()
// Create a custom template file
tmplPath := tmpDir + "/custom.conf"
os.WriteFile(tmplPath, []byte("[Interface]\n# Custom template\n"), 0644)
origWgConfTemplate := WgConfTemplate
WgConfTemplate = tmplPath
defer func() { WgConfTemplate = origWgConfTemplate }()
settings := model.GlobalSetting{
ConfigFilePath: tmpDir + "/wg0.conf",
}
server := model.Server{
Interface: &model.ServerInterface{
Addresses: []string{"10.0.0.0/24"},
ListenPort: 51820,
},
KeyPair: &model.ServerKeypair{
PrivateKey: "privkey",
PublicKey: "pubkey",
},
}
err := WriteWireGuardServerConfig(os.DirFS(tmpDir), server, nil, nil, settings)
require.NoError(t, err)
content, _ := os.ReadFile(tmpDir + "/wg0.conf")
assert.Contains(t, string(content), "Custom template")
}
func TestWriteWireGuardServerConfig_WithClientNotes(t *testing.T) {
tmpDir := t.TempDir()
settings := model.GlobalSetting{
ConfigFilePath: tmpDir + "/wg0.conf",
}
server := model.Server{
Interface: &model.ServerInterface{
Addresses: []string{"10.0.0.0/24"},
ListenPort: 51820,
},
KeyPair: &model.ServerKeypair{
PrivateKey: "privkey",
PublicKey: "pubkey",
},
}
clients := []model.ClientData{
{
Client: &model.Client{
ID: "c1",
Name: "Client With Notes",
PublicKey: "clientpub",
PresharedKey: "clientpsk",
AllocatedIPs: []string{"10.0.0.2/32"},
AllowedIPs: []string{"0.0.0.0/0"},
AdditionalNotes: "Line one\nLine two\nLine three",
Enabled: true,
},
},
}
err := WriteWireGuardServerConfig(os.DirFS("../templates"), server, clients, nil, settings)
require.NoError(t, err)
content, _ := os.ReadFile(tmpDir + "/wg0.conf")
assert.Contains(t, string(content), "[Peer]")
}
func TestWriteWireGuardServerConfig_InvalidCustomTemplateFile(t *testing.T) {
origWgConfTemplate := WgConfTemplate
WgConfTemplate = "/nonexistent/template.conf"
defer func() { WgConfTemplate = origWgConfTemplate }()
settings := model.GlobalSetting{
ConfigFilePath: "/tmp/wg0.conf",
}
server := model.Server{
Interface: &model.ServerInterface{},
KeyPair: &model.ServerKeypair{},
}
err := WriteWireGuardServerConfig(os.DirFS("."), server, nil, nil, settings)
assert.Error(t, err)
}
// --- StringFromEmbedFile ---
func TestWriteWireGuardServerConfig_InvalidConfigPath(t *testing.T) {
settings := model.GlobalSetting{
ConfigFilePath: "/nonexistent/dir/wg0.conf",
}
server := model.Server{
Interface: &model.ServerInterface{
Addresses: []string{"10.0.0.0/24"},
ListenPort: 51820,
},
KeyPair: &model.ServerKeypair{
PrivateKey: "privkey",
PublicKey: "pubkey",
},
}
err := WriteWireGuardServerConfig(os.DirFS("../templates"), server, nil, nil, settings)
assert.Error(t, err)
}
func TestStringFromEmbedFile_NotFound(t *testing.T) {
fsys := os.DirFS(t.TempDir())
_, err := StringFromEmbedFile(fsys, "nonexistent.conf")
assert.Error(t, err)
}
// --- GetDBUserCRC32 ---
func TestGetDBUserCRC32_Consistent(t *testing.T) {
user := model.User{
Username: "testuser",
Email: "test@example.com",
Admin: true,
}
hash1 := GetDBUserCRC32(user)
hash2 := GetDBUserCRC32(user)
assert.Equal(t, hash1, hash2, "CRC32 should be consistent for same input")
}
func TestGetDBUserCRC32_Different(t *testing.T) {
user1 := model.User{Username: "user1", Email: "a@test.com"}
user2 := model.User{Username: "user2", Email: "b@test.com"}
hash1 := GetDBUserCRC32(user1)
hash2 := GetDBUserCRC32(user2)
assert.NotEqual(t, hash1, hash2)
}
// --- LookupEnvOrFile ---
func TestLookupEnvOrFile_WithFile(t *testing.T) {
tmpDir := t.TempDir()
filePath := tmpDir + "/secret.txt"
os.WriteFile(filePath, []byte("mysecret"), 0644)
os.Setenv("TEST_SECRET_FILE", filePath)
defer os.Unsetenv("TEST_SECRET_FILE")
result := LookupEnvOrFile("TEST_SECRET_FILE", "default")
assert.Equal(t, "mysecret", result)
}
func TestLookupEnvOrFile_FileNotFound(t *testing.T) {
os.Setenv("TEST_SECRET_FILE", "/nonexistent/file")
defer os.Unsetenv("TEST_SECRET_FILE")
result := LookupEnvOrFile("TEST_SECRET_FILE", "default")
assert.Equal(t, "default", result)
}
func TestLookupEnvOrFile_EnvNotSet(t *testing.T) {
os.Unsetenv("TEST_SECRET_FILE_UNSET")
result := LookupEnvOrFile("TEST_SECRET_FILE_UNSET", "mydefault")
assert.Equal(t, "mydefault", result)
}