Skip to content

Commit c4a7c5d

Browse files
committed
Add test
1 parent 50238ab commit c4a7c5d

File tree

1 file changed

+298
-0
lines changed

1 file changed

+298
-0
lines changed

client/server/setconfig_test.go

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
package server
2+
3+
import (
4+
"context"
5+
"os/user"
6+
"path/filepath"
7+
"reflect"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/require"
12+
"google.golang.org/protobuf/types/known/durationpb"
13+
14+
"github.com/netbirdio/netbird/client/internal/profilemanager"
15+
"github.com/netbirdio/netbird/client/proto"
16+
)
17+
18+
// TestSetConfig_AllFieldsSaved ensures that all fields in SetConfigRequest are properly saved to the config.
19+
// This test uses reflection to detect when new fields are added but not handled in SetConfig.
20+
func TestSetConfig_AllFieldsSaved(t *testing.T) {
21+
tempDir := t.TempDir()
22+
origDefaultProfileDir := profilemanager.DefaultConfigPathDir
23+
origDefaultConfigPath := profilemanager.DefaultConfigPath
24+
origActiveProfileStatePath := profilemanager.ActiveProfileStatePath
25+
profilemanager.ConfigDirOverride = tempDir
26+
profilemanager.DefaultConfigPathDir = tempDir
27+
profilemanager.ActiveProfileStatePath = tempDir + "/active_profile.json"
28+
profilemanager.DefaultConfigPath = filepath.Join(tempDir, "default.json")
29+
t.Cleanup(func() {
30+
profilemanager.DefaultConfigPathDir = origDefaultProfileDir
31+
profilemanager.ActiveProfileStatePath = origActiveProfileStatePath
32+
profilemanager.DefaultConfigPath = origDefaultConfigPath
33+
profilemanager.ConfigDirOverride = ""
34+
})
35+
36+
currUser, err := user.Current()
37+
require.NoError(t, err)
38+
39+
profName := "test-profile"
40+
41+
ic := profilemanager.ConfigInput{
42+
ConfigPath: filepath.Join(tempDir, profName+".json"),
43+
ManagementURL: "https://api.netbird.io:443",
44+
}
45+
_, err = profilemanager.UpdateOrCreateConfig(ic)
46+
require.NoError(t, err)
47+
48+
pm := profilemanager.ServiceManager{}
49+
err = pm.SetActiveProfileState(&profilemanager.ActiveProfileState{
50+
Name: profName,
51+
Username: currUser.Username,
52+
})
53+
require.NoError(t, err)
54+
55+
ctx := context.Background()
56+
s := New(ctx, "console", "", false, false)
57+
58+
rosenpassEnabled := true
59+
rosenpassPermissive := true
60+
serverSSHAllowed := true
61+
interfaceName := "utun100"
62+
wireguardPort := int64(51820)
63+
preSharedKey := "test-psk"
64+
disableAutoConnect := true
65+
networkMonitor := true
66+
disableClientRoutes := true
67+
disableServerRoutes := true
68+
disableDNS := true
69+
disableFirewall := true
70+
blockLANAccess := true
71+
disableNotifications := true
72+
lazyConnectionEnabled := true
73+
blockInbound := true
74+
mtu := int64(1280)
75+
76+
req := &proto.SetConfigRequest{
77+
ProfileName: profName,
78+
Username: currUser.Username,
79+
ManagementUrl: "https://new-api.netbird.io:443",
80+
AdminURL: "https://new-admin.netbird.io",
81+
RosenpassEnabled: &rosenpassEnabled,
82+
RosenpassPermissive: &rosenpassPermissive,
83+
ServerSSHAllowed: &serverSSHAllowed,
84+
InterfaceName: &interfaceName,
85+
WireguardPort: &wireguardPort,
86+
OptionalPreSharedKey: &preSharedKey,
87+
DisableAutoConnect: &disableAutoConnect,
88+
NetworkMonitor: &networkMonitor,
89+
DisableClientRoutes: &disableClientRoutes,
90+
DisableServerRoutes: &disableServerRoutes,
91+
DisableDns: &disableDNS,
92+
DisableFirewall: &disableFirewall,
93+
BlockLanAccess: &blockLANAccess,
94+
DisableNotifications: &disableNotifications,
95+
LazyConnectionEnabled: &lazyConnectionEnabled,
96+
BlockInbound: &blockInbound,
97+
NatExternalIPs: []string{"1.2.3.4", "5.6.7.8"},
98+
CleanNATExternalIPs: false,
99+
CustomDNSAddress: []byte("1.1.1.1:53"),
100+
ExtraIFaceBlacklist: []string{"eth1", "eth2"},
101+
DnsLabels: []string{"label1", "label2"},
102+
CleanDNSLabels: false,
103+
DnsRouteInterval: durationpb.New(2 * time.Minute),
104+
Mtu: &mtu,
105+
}
106+
107+
_, err = s.SetConfig(ctx, req)
108+
require.NoError(t, err)
109+
110+
profState := profilemanager.ActiveProfileState{
111+
Name: profName,
112+
Username: currUser.Username,
113+
}
114+
cfgPath, err := profState.FilePath()
115+
require.NoError(t, err)
116+
117+
cfg, err := profilemanager.GetConfig(cfgPath)
118+
require.NoError(t, err)
119+
120+
require.Equal(t, "https://new-api.netbird.io:443", cfg.ManagementURL.String())
121+
require.Equal(t, "https://new-admin.netbird.io:443", cfg.AdminURL.String())
122+
require.Equal(t, rosenpassEnabled, cfg.RosenpassEnabled)
123+
require.Equal(t, rosenpassPermissive, cfg.RosenpassPermissive)
124+
require.NotNil(t, cfg.ServerSSHAllowed)
125+
require.Equal(t, serverSSHAllowed, *cfg.ServerSSHAllowed)
126+
require.Equal(t, interfaceName, cfg.WgIface)
127+
require.Equal(t, int(wireguardPort), cfg.WgPort)
128+
require.Equal(t, preSharedKey, cfg.PreSharedKey)
129+
require.Equal(t, disableAutoConnect, cfg.DisableAutoConnect)
130+
require.NotNil(t, cfg.NetworkMonitor)
131+
require.Equal(t, networkMonitor, *cfg.NetworkMonitor)
132+
require.Equal(t, disableClientRoutes, cfg.DisableClientRoutes)
133+
require.Equal(t, disableServerRoutes, cfg.DisableServerRoutes)
134+
require.Equal(t, disableDNS, cfg.DisableDNS)
135+
require.Equal(t, disableFirewall, cfg.DisableFirewall)
136+
require.Equal(t, blockLANAccess, cfg.BlockLANAccess)
137+
require.NotNil(t, cfg.DisableNotifications)
138+
require.Equal(t, disableNotifications, *cfg.DisableNotifications)
139+
require.Equal(t, lazyConnectionEnabled, cfg.LazyConnectionEnabled)
140+
require.Equal(t, blockInbound, cfg.BlockInbound)
141+
require.Equal(t, []string{"1.2.3.4", "5.6.7.8"}, cfg.NATExternalIPs)
142+
require.Equal(t, "1.1.1.1:53", cfg.CustomDNSAddress)
143+
// IFaceBlackList contains defaults + extras
144+
require.Contains(t, cfg.IFaceBlackList, "eth1")
145+
require.Contains(t, cfg.IFaceBlackList, "eth2")
146+
require.Equal(t, []string{"label1", "label2"}, cfg.DNSLabels.ToPunycodeList())
147+
require.Equal(t, 2*time.Minute, cfg.DNSRouteInterval)
148+
require.Equal(t, uint16(mtu), cfg.MTU)
149+
150+
verifyAllFieldsCovered(t, req)
151+
}
152+
153+
// verifyAllFieldsCovered uses reflection to ensure we're testing all fields in SetConfigRequest.
154+
// If a new field is added to SetConfigRequest, this function will fail the test,
155+
// forcing the developer to update both the SetConfig handler and this test.
156+
func verifyAllFieldsCovered(t *testing.T, req *proto.SetConfigRequest) {
157+
t.Helper()
158+
159+
metadataFields := map[string]bool{
160+
"state": true, // protobuf internal
161+
"sizeCache": true, // protobuf internal
162+
"unknownFields": true, // protobuf internal
163+
"Username": true, // metadata
164+
"ProfileName": true, // metadata
165+
"CleanNATExternalIPs": true, // control flag for clearing
166+
"CleanDNSLabels": true, // control flag for clearing
167+
}
168+
169+
expectedFields := map[string]bool{
170+
"ManagementUrl": true,
171+
"AdminURL": true,
172+
"RosenpassEnabled": true,
173+
"RosenpassPermissive": true,
174+
"ServerSSHAllowed": true,
175+
"InterfaceName": true,
176+
"WireguardPort": true,
177+
"OptionalPreSharedKey": true,
178+
"DisableAutoConnect": true,
179+
"NetworkMonitor": true,
180+
"DisableClientRoutes": true,
181+
"DisableServerRoutes": true,
182+
"DisableDns": true,
183+
"DisableFirewall": true,
184+
"BlockLanAccess": true,
185+
"DisableNotifications": true,
186+
"LazyConnectionEnabled": true,
187+
"BlockInbound": true,
188+
"NatExternalIPs": true,
189+
"CustomDNSAddress": true,
190+
"ExtraIFaceBlacklist": true,
191+
"DnsLabels": true,
192+
"DnsRouteInterval": true,
193+
"Mtu": true,
194+
}
195+
196+
val := reflect.ValueOf(req).Elem()
197+
typ := val.Type()
198+
199+
var unexpectedFields []string
200+
for i := 0; i < val.NumField(); i++ {
201+
field := typ.Field(i)
202+
fieldName := field.Name
203+
204+
if metadataFields[fieldName] {
205+
continue
206+
}
207+
208+
if !expectedFields[fieldName] {
209+
unexpectedFields = append(unexpectedFields, fieldName)
210+
}
211+
}
212+
213+
if len(unexpectedFields) > 0 {
214+
t.Fatalf("New field(s) detected in SetConfigRequest: %v", unexpectedFields)
215+
}
216+
}
217+
218+
// TestCLIFlags_MappedToSetConfig ensures all CLI flags that modify config are properly mapped to SetConfigRequest.
219+
// This test catches bugs where a new CLI flag is added but not wired to the SetConfigRequest in setupSetConfigReq.
220+
func TestCLIFlags_MappedToSetConfig(t *testing.T) {
221+
// Map of CLI flag names to their corresponding SetConfigRequest field names.
222+
// This map must be updated when adding new config-related CLI flags.
223+
flagToField := map[string]string{
224+
"management-url": "ManagementUrl",
225+
"admin-url": "AdminURL",
226+
"enable-rosenpass": "RosenpassEnabled",
227+
"rosenpass-permissive": "RosenpassPermissive",
228+
"allow-server-ssh": "ServerSSHAllowed",
229+
"interface-name": "InterfaceName",
230+
"wireguard-port": "WireguardPort",
231+
"preshared-key": "OptionalPreSharedKey",
232+
"disable-auto-connect": "DisableAutoConnect",
233+
"network-monitor": "NetworkMonitor",
234+
"disable-client-routes": "DisableClientRoutes",
235+
"disable-server-routes": "DisableServerRoutes",
236+
"disable-dns": "DisableDns",
237+
"disable-firewall": "DisableFirewall",
238+
"block-lan-access": "BlockLanAccess",
239+
"block-inbound": "BlockInbound",
240+
"enable-lazy-connection": "LazyConnectionEnabled",
241+
"external-ip-map": "NatExternalIPs",
242+
"dns-resolver-address": "CustomDNSAddress",
243+
"extra-iface-blacklist": "ExtraIFaceBlacklist",
244+
"extra-dns-labels": "DnsLabels",
245+
"dns-router-interval": "DnsRouteInterval",
246+
"mtu": "Mtu",
247+
}
248+
249+
// SetConfigRequest fields that don't have CLI flags (settable only via UI or other means).
250+
fieldsWithoutCLIFlags := map[string]bool{
251+
"DisableNotifications": true, // Only settable via UI
252+
}
253+
254+
// Get all SetConfigRequest fields to verify our map is complete.
255+
req := &proto.SetConfigRequest{}
256+
val := reflect.ValueOf(req).Elem()
257+
typ := val.Type()
258+
259+
var unmappedFields []string
260+
for i := 0; i < val.NumField(); i++ {
261+
field := typ.Field(i)
262+
fieldName := field.Name
263+
264+
// Skip protobuf internal fields and metadata fields.
265+
if fieldName == "state" || fieldName == "sizeCache" || fieldName == "unknownFields" {
266+
continue
267+
}
268+
if fieldName == "Username" || fieldName == "ProfileName" {
269+
continue
270+
}
271+
if fieldName == "CleanNATExternalIPs" || fieldName == "CleanDNSLabels" {
272+
continue
273+
}
274+
275+
// Check if this field is either mapped to a CLI flag or explicitly documented as having no CLI flag.
276+
mappedToCLI := false
277+
for _, mappedField := range flagToField {
278+
if mappedField == fieldName {
279+
mappedToCLI = true
280+
break
281+
}
282+
}
283+
284+
hasNoCLIFlag := fieldsWithoutCLIFlags[fieldName]
285+
286+
if !mappedToCLI && !hasNoCLIFlag {
287+
unmappedFields = append(unmappedFields, fieldName)
288+
}
289+
}
290+
291+
if len(unmappedFields) > 0 {
292+
t.Fatalf("SetConfigRequest field(s) not documented: %v\n"+
293+
"Either add the CLI flag to flagToField map, or if there's no CLI flag for this field, "+
294+
"add it to fieldsWithoutCLIFlags map with a comment explaining why.", unmappedFields)
295+
}
296+
297+
t.Log("All SetConfigRequest fields are properly documented")
298+
}

0 commit comments

Comments
 (0)