package wireguard import ( "net" "os" "path/filepath" "strings" "testing" "time" ) func newTestManager(t *testing.T) *Manager { t.Helper() dir := t.TempDir() return NewManager(dir, filepath.Join(dir, "wg0.conf")) } // --- Config tests --- func TestGetConfig_Defaults(t *testing.T) { m := newTestManager(t) cfg, err := m.GetConfig() if err != nil { t.Fatalf("GetConfig: %v", err) } if cfg.ListenPort != 51820 { t.Errorf("default ListenPort = %d, want 51820", cfg.ListenPort) } if cfg.Address != "10.8.0.1/24" { t.Errorf("default Address = %q, want 10.8.0.1/24", cfg.Address) } if cfg.Enabled { t.Error("default Enabled should be false") } } func TestSaveAndGetConfig(t *testing.T) { m := newTestManager(t) cfg := &Config{ Enabled: true, ListenPort: 51820, Address: "10.100.0.1/24", Endpoint: "vpn.example.com:51820", DNS: "10.100.0.1", } if err := m.SaveConfig(cfg); err != nil { t.Fatalf("SaveConfig: %v", err) } got, err := m.GetConfig() if err != nil { t.Fatalf("GetConfig: %v", err) } if got.Address != cfg.Address { t.Errorf("Address = %q, want %q", got.Address, cfg.Address) } if got.Endpoint != cfg.Endpoint { t.Errorf("Endpoint = %q, want %q", got.Endpoint, cfg.Endpoint) } if got.DNS != cfg.DNS { t.Errorf("DNS = %q, want %q", got.DNS, cfg.DNS) } } // --- Peer management tests --- func writeTestSecrets(t *testing.T, m *Manager) { t.Helper() if err := os.MkdirAll(m.vpnDir(), 0755); err != nil { t.Fatal(err) } content := "privateKey: fakeprivkey123\npublicKey: fakepubkey456\n" if err := os.WriteFile(m.secretsFilePath(), []byte(content), 0600); err != nil { t.Fatal(err) } } func TestListPeers_Empty(t *testing.T) { m := newTestManager(t) peers, err := m.ListPeers() if err != nil { t.Fatalf("ListPeers: %v", err) } if len(peers) != 0 { t.Errorf("expected 0 peers, got %d", len(peers)) } } func TestSaveAndGetPeer(t *testing.T) { m := newTestManager(t) p := &Peer{ ID: "test-id-1", Name: "my-phone", PublicKey: "pubkey1", PrivateKey: "privkey1", AllowedIPs: "10.8.0.2/32", CreatedAt: time.Now().UTC(), } if err := m.savePeer(p); err != nil { t.Fatalf("savePeer: %v", err) } got, err := m.GetPeer("test-id-1") if err != nil { t.Fatalf("GetPeer: %v", err) } if got.Name != "my-phone" { t.Errorf("Name = %q, want %q", got.Name, "my-phone") } if got.AllowedIPs != "10.8.0.2/32" { t.Errorf("AllowedIPs = %q, want 10.8.0.2/32", got.AllowedIPs) } } func TestDeletePeer(t *testing.T) { m := newTestManager(t) p := &Peer{ID: "del-me", Name: "temp", PublicKey: "k", PrivateKey: "k", AllowedIPs: "10.8.0.2/32", CreatedAt: time.Now()} if err := m.savePeer(p); err != nil { t.Fatal(err) } if err := m.DeletePeer("del-me"); err != nil { t.Fatalf("DeletePeer: %v", err) } if _, err := m.GetPeer("del-me"); err == nil { t.Error("expected error getting deleted peer, got nil") } } func TestDeletePeer_NotFound(t *testing.T) { m := newTestManager(t) if err := m.DeletePeer("nope"); err == nil { t.Error("expected error for missing peer") } } // --- IP assignment tests --- func TestNextAvailableIP_FirstPeer(t *testing.T) { m := newTestManager(t) // Server is 10.8.0.1/24; first peer should get 10.8.0.2. ip, err := m.nextAvailableIP("10.8.0.1/24") if err != nil { t.Fatalf("nextAvailableIP: %v", err) } if ip != "10.8.0.2" { t.Errorf("first peer IP = %q, want 10.8.0.2", ip) } } func TestNextAvailableIP_SkipsTakenIPs(t *testing.T) { m := newTestManager(t) // Pre-populate .2 and .3 for _, pair := range []struct{ id, ip string }{ {"p1", "10.8.0.2/32"}, {"p2", "10.8.0.3/32"}, } { if err := m.savePeer(&Peer{ID: pair.id, AllowedIPs: pair.ip, CreatedAt: time.Now()}); err != nil { t.Fatal(err) } } ip, err := m.nextAvailableIP("10.8.0.1/24") if err != nil { t.Fatalf("nextAvailableIP: %v", err) } if ip != "10.8.0.4" { t.Errorf("next IP = %q, want 10.8.0.4", ip) } } func TestNextAvailableIP_InvalidCIDR(t *testing.T) { m := newTestManager(t) if _, err := m.nextAvailableIP("not-a-cidr"); err == nil { t.Error("expected error for invalid CIDR") } } // --- GetPublicKey --- func TestGetPublicKey_NoSecrets(t *testing.T) { m := newTestManager(t) if key := m.GetPublicKey(); key != "" { t.Errorf("expected empty key, got %q", key) } } func TestGetPublicKey_WithSecrets(t *testing.T) { m := newTestManager(t) writeTestSecrets(t, m) if key := m.GetPublicKey(); key != "fakepubkey456" { t.Errorf("public key = %q, want fakepubkey456", key) } } // --- Peer config text --- func TestGeneratePeerConfigText(t *testing.T) { m := newTestManager(t) writeTestSecrets(t, m) if err := m.SaveConfig(&Config{ Enabled: true, ListenPort: 51820, Address: "10.8.0.1/24", Endpoint: "vpn.example.com", DNS: "10.8.0.1", }); err != nil { t.Fatal(err) } peer := &Peer{ ID: "peer-1", Name: "laptop", PublicKey: "clientpub", PrivateKey: "clientpriv", AllowedIPs: "10.8.0.2/32", CreatedAt: time.Now(), } if err := m.savePeer(peer); err != nil { t.Fatal(err) } text, err := m.GeneratePeerConfigText("peer-1") if err != nil { t.Fatalf("GeneratePeerConfigText: %v", err) } checks := []string{ "[Interface]", "PrivateKey = clientpriv", "Address = 10.8.0.2/32", "DNS = 10.8.0.1", "[Peer]", "PublicKey = fakepubkey456", "Endpoint = vpn.example.com:51820", "AllowedIPs =", "PersistentKeepalive = 25", } for _, want := range checks { if !strings.Contains(text, want) { t.Errorf("peer config missing %q\nFull config:\n%s", want, text) } } } func TestGeneratePeerConfigText_NoSecrets(t *testing.T) { m := newTestManager(t) _ = m.SaveConfig(&Config{Enabled: true, ListenPort: 51820, Address: "10.8.0.1/24"}) _ = m.savePeer(&Peer{ID: "p1", AllowedIPs: "10.8.0.2/32", CreatedAt: time.Now()}) if _, err := m.GeneratePeerConfigText("p1"); err == nil { t.Error("expected error when secrets not generated") } } // --- renderWgConfig --- func TestRenderWgConfig(t *testing.T) { m := newTestManager(t) cfg := &Config{ListenPort: 51820, Address: "10.8.0.1/24"} srv := &secrets{PrivateKey: "servpriv", PublicKey: "servpub"} peers := []*Peer{ {Name: "alice", PublicKey: "alicepub", AllowedIPs: "10.8.0.2/32"}, } out := m.renderWgConfig(cfg, srv, peers) checks := []string{ "[Interface]", "PrivateKey = servpriv", "Address = 10.8.0.1/24", "ListenPort = 51820", "[Peer]", "# alice", "PublicKey = alicepub", "AllowedIPs = 10.8.0.2/32", } for _, want := range checks { if !strings.Contains(out, want) { t.Errorf("wg config missing %q\nFull:\n%s", want, out) } } } // --- IP helpers --- func TestCloneIP(t *testing.T) { ip := net.ParseIP("10.8.0.1").To4() clone := cloneIP(ip) clone[3] = 99 if ip[3] == 99 { t.Error("cloneIP should not share underlying array") } } func TestIncrementIP(t *testing.T) { ip := net.ParseIP("10.8.0.1").To4() incrementIP(ip) if ip.String() != "10.8.0.2" { t.Errorf("incrementIP: got %s, want 10.8.0.2", ip) } } func TestBroadcastAddr(t *testing.T) { _, ipNet, _ := net.ParseCIDR("10.8.0.0/24") if got := broadcastAddr(ipNet); got != "10.8.0.255" { t.Errorf("broadcastAddr = %q, want 10.8.0.255", got) } }