summaryrefslogtreecommitdiff
path: root/internal/network/network_test.go
diff options
context:
space:
mode:
authorJames O'Doherty <james@theodohertyfamily.com>2026-06-13 11:51:04 -0400
committerJames O'Doherty <james@theodohertyfamily.com>2026-06-13 11:51:04 -0400
commit29621ecbd1e77e6e1a70b6b3ea8fbe3a56e47df3 (patch)
treefa54976bbb0c4e9db59c983e7cb4e60c5119d18b /internal/network/network_test.go
parentf8afb7d5889f5c8b6ea256fd078fa8426d21c7be (diff)
refactor: implement dependency injection and enable parallel testing
This commit refactors the core system operations to use a manager-based dependency injection pattern, eliminating global state and resolving data races in the test suite. Architecture: - Introduced NetworkManager and NetworkOps interface in internal/network to abstract netlink calls. - Introduced MountOps and FileSystem interfaces in internal/namespace to abstract mount and filesystem operations. - Introduced TunnelManager in internal/wireguard to coordinate tunnel lifecycle using the new abstractions. - Updated internal/cli and internal/manager to use these managers. Testing: - Restored t.Parallel() to unit tests in internal/network and internal/wireguard. - Implemented setupParallelEnv and an enhanced mockFS in wireguard_unit_test.go to ensure complete test isolation. - Added bootstrap_test.go to verify launcher preparation logic in internal/namespace without requiring syscall.Exec. - Resolved data races in internal/network tests. CLI: - Added support for -h, --help, and -help flags for the main command. Verification: - Passed all tests (unit, integration, E2E). - Verified zero data races with 'go test -race'. - Passed golangci-lint and go vet.
Diffstat (limited to 'internal/network/network_test.go')
-rw-r--r--internal/network/network_test.go163
1 files changed, 163 insertions, 0 deletions
diff --git a/internal/network/network_test.go b/internal/network/network_test.go
new file mode 100644
index 0000000..b598484
--- /dev/null
+++ b/internal/network/network_test.go
@@ -0,0 +1,163 @@
+//go:build linux
+
+package network
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/vishvananda/netlink"
+)
+
+// mockNetworkOps allows us to control the behavior of netlink calls.
+type mockNetworkOps struct {
+ linkByNameFunc func(name string) (netlink.Link, error)
+ linkSetMTUFunc func(link netlink.Link, mtu int) error
+ linkSetUpFunc func(link netlink.Link) error
+ addrAddFunc func(link netlink.Link, addr *netlink.Addr) error
+ routeAddFunc func(route *netlink.Route) error
+ routeReplaceFunc func(route *netlink.Route) error
+}
+
+func (m *mockNetworkOps) LinkList() ([]netlink.Link, error) { return nil, nil }
+func (m *mockNetworkOps) LinkByName(name string) (netlink.Link, error) {
+ if m.linkByNameFunc != nil {
+ return m.linkByNameFunc(name)
+ }
+ return nil, fmt.Errorf("not implemented")
+}
+func (m *mockNetworkOps) LinkSetMTU(link netlink.Link, mtu int) error {
+ if m.linkSetMTUFunc != nil {
+ return m.linkSetMTUFunc(link, mtu)
+ }
+ return nil
+}
+func (m *mockNetworkOps) LinkSetUp(link netlink.Link) error {
+ if m.linkSetUpFunc != nil {
+ return m.linkSetUpFunc(link)
+ }
+ return nil
+}
+func (m *mockNetworkOps) AddrAdd(link netlink.Link, addr *netlink.Addr) error {
+ if m.addrAddFunc != nil {
+ return m.addrAddFunc(link, addr)
+ }
+ return nil
+}
+func (m *mockNetworkOps) RouteAdd(route *netlink.Route) error {
+ if m.routeAddFunc != nil {
+ return m.routeAddFunc(route)
+ }
+ return nil
+}
+func (m *mockNetworkOps) RouteReplace(route *netlink.Route) error {
+ if m.routeReplaceFunc != nil {
+ return m.routeReplaceFunc(route)
+ }
+ return nil
+}
+
+// mockLink implements netlink.Link.
+type mockLink struct {
+ name string
+ idx int
+}
+
+func (m *mockLink) Type() string {
+ return "mock"
+}
+
+func (m *mockLink) Attrs() *netlink.LinkAttrs {
+ return &netlink.LinkAttrs{Name: m.name, Index: m.idx}
+}
+
+func TestConfigureInterface_Success(t *testing.T) {
+ t.Parallel()
+ mock := &mockNetworkOps{
+ linkByNameFunc: func(name string) (netlink.Link, error) {
+ return &mockLink{name: name, idx: 1}, nil
+ },
+ }
+ nm := &NetworkManager{Ops: mock}
+
+ err := nm.ConfigureInterface("tun0", "10.0.0.1/24", 1420)
+ if err != nil {
+ t.Errorf("expected success, got %v", err)
+ }
+}
+
+func TestConfigureInterface_RouteFallback(t *testing.T) {
+ t.Parallel()
+ routeAddCalled := false
+ routeReplaceCalled := false
+
+ mock := &mockNetworkOps{
+ linkByNameFunc: func(name string) (netlink.Link, error) {
+ return &mockLink{name: name, idx: 1}, nil
+ },
+ routeAddFunc: func(route *netlink.Route) error {
+ routeAddCalled = true
+ return errors.New("file exists") // Simulate EEXIST
+ },
+ routeReplaceFunc: func(route *netlink.Route) error {
+ routeReplaceCalled = true
+ return nil
+ },
+ }
+ nm := &NetworkManager{Ops: mock}
+
+ err := nm.ConfigureInterface("tun0", "10.0.0.1/24", 1420)
+ if err != nil {
+ t.Errorf("expected success after fallback, got %v", err)
+ }
+ if !routeAddCalled {
+ t.Error("expected RouteAdd to be called first")
+ }
+ if !routeReplaceCalled {
+ t.Error("expected RouteReplace to be called after RouteAdd fails with 'file exists'")
+ }
+}
+
+func TestConfigureInterface_RouteFailure(t *testing.T) {
+ t.Parallel()
+ mock := &mockNetworkOps{
+ linkByNameFunc: func(name string) (netlink.Link, error) {
+ return &mockLink{name: name, idx: 1}, nil
+ },
+ routeAddFunc: func(route *netlink.Route) error {
+ return errors.New("critical network failure")
+ },
+ routeReplaceFunc: func(route *netlink.Route) error {
+ return errors.New("critical network failure")
+ },
+ }
+ nm := &NetworkManager{Ops: mock}
+
+ err := nm.ConfigureInterface("tun0", "10.0.0.1/24", 1420)
+ if err == nil {
+ t.Error("expected error when both RouteAdd and RouteReplace fail, got nil")
+ }
+ if !strings.Contains(err.Error(), "failed to configure default route") {
+ t.Errorf("expected route error, got: %v", err)
+ }
+}
+
+func TestConfigureInterface_LinkNotFound(t *testing.T) {
+ t.Parallel()
+ mock := &mockNetworkOps{
+ linkByNameFunc: func(name string) (netlink.Link, error) {
+ return nil, errors.New("no such device")
+ },
+ }
+ nm := &NetworkManager{Ops: mock}
+
+ err := nm.ConfigureInterface("nonexistent", "10.0.0.1/24", 1420)
+ if err == nil {
+ t.Error("expected error when link is not found, got nil")
+ }
+ if !strings.Contains(err.Error(), "failed to find link") {
+ t.Errorf("expected link not found error, got: %v", err)
+ }
+}