mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-22 22:59:09 +02:00
Store the Android split tunnelling settings per profile (#7349)
Which applications the tunnel carries belongs with the rest of a profile's preferences rather than on the Android side, so the choice follows the profile the user is on. Adds a split tunnel store beside the SSH session store, over a "split-tunnel" namespace holding the mode and both selections. The two selections are kept apart because the platform applies an allow list or a deny list and never both, and so that switching mode does not throw away the picks made in the other one. Only the store itself needs the android build tag; the rest stays untagged so it is covered by the package's host tests.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package android
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeSplitTunnelMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mode string
|
||||
want string
|
||||
}{
|
||||
{name: "exclude is kept", mode: SplitTunnelModeExclude, want: SplitTunnelModeExclude},
|
||||
{name: "include is kept", mode: SplitTunnelModeInclude, want: SplitTunnelModeInclude},
|
||||
{name: "off is kept", mode: SplitTunnelModeOff, want: SplitTunnelModeOff},
|
||||
{name: "empty falls back to off", mode: "", want: SplitTunnelModeOff},
|
||||
{name: "a mode from a newer build falls back to off", mode: "only-work-apps", want: SplitTunnelModeOff},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := normalizeSplitTunnelMode(tt.mode); got != tt.want {
|
||||
t.Errorf("normalizeSplitTunnelMode(%q) = %q, want %q", tt.mode, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsFromSection(t *testing.T) {
|
||||
got := settingsFromSection(splitTunnelSection{
|
||||
Mode: SplitTunnelModeExclude,
|
||||
Excluded: []string{"com.example.a", "com.example.b"},
|
||||
Included: []string{"com.example.c"},
|
||||
})
|
||||
|
||||
if got.Mode != SplitTunnelModeExclude {
|
||||
t.Errorf("mode = %q, want %q", got.Mode, SplitTunnelModeExclude)
|
||||
}
|
||||
if got.Excluded.Size() != 2 || got.Excluded.Get(0) != "com.example.a" {
|
||||
t.Errorf("excluded = %v, want the two stored packages", packagesOf(got.Excluded))
|
||||
}
|
||||
if got.Included.Size() != 1 || got.Included.Get(0) != "com.example.c" {
|
||||
t.Errorf("included = %v, want the stored package", packagesOf(got.Included))
|
||||
}
|
||||
}
|
||||
|
||||
// A profile that has never stored anything decodes into an empty section, and
|
||||
// must come back as settings that carry every application rather than as nil
|
||||
// lists the caller would have to guard against.
|
||||
func TestSettingsFromEmptySectionCarriesEverything(t *testing.T) {
|
||||
got := settingsFromSection(splitTunnelSection{})
|
||||
|
||||
if got.Mode != SplitTunnelModeOff {
|
||||
t.Errorf("mode = %q, want %q", got.Mode, SplitTunnelModeOff)
|
||||
}
|
||||
if got.Excluded == nil || got.Included == nil {
|
||||
t.Fatal("both selections must be usable lists, not nil")
|
||||
}
|
||||
if got.Excluded.Size() != 0 || got.Included.Size() != 0 {
|
||||
t.Errorf("selections = %v/%v, want both empty", packagesOf(got.Excluded), packagesOf(got.Included))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSectionFromSettingsRoundTrip(t *testing.T) {
|
||||
settings := NewSplitTunnelSettings()
|
||||
settings.Mode = SplitTunnelModeInclude
|
||||
settings.Included.Add("com.example.a")
|
||||
settings.Excluded.Add("com.example.b")
|
||||
|
||||
section := sectionFromSettings(settings)
|
||||
back := settingsFromSection(section)
|
||||
|
||||
if back.Mode != SplitTunnelModeInclude {
|
||||
t.Errorf("mode = %q, want %q", back.Mode, SplitTunnelModeInclude)
|
||||
}
|
||||
if !reflect.DeepEqual(packagesOf(back.Included), []string{"com.example.a"}) {
|
||||
t.Errorf("included = %v, want [com.example.a]", packagesOf(back.Included))
|
||||
}
|
||||
// The inactive selection survives, so switching mode back does not make the
|
||||
// user pick their applications again.
|
||||
if !reflect.DeepEqual(packagesOf(back.Excluded), []string{"com.example.b"}) {
|
||||
t.Errorf("excluded = %v, want [com.example.b]", packagesOf(back.Excluded))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSectionFromNilSettings(t *testing.T) {
|
||||
section := sectionFromSettings(nil)
|
||||
|
||||
if section.Mode != SplitTunnelModeOff {
|
||||
t.Errorf("mode = %q, want %q", section.Mode, SplitTunnelModeOff)
|
||||
}
|
||||
if len(section.Excluded) != 0 || len(section.Included) != 0 {
|
||||
t.Errorf("selections = %v/%v, want both empty", section.Excluded, section.Included)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageListIgnoresEmptyAndBounds(t *testing.T) {
|
||||
list := NewPackageList()
|
||||
list.Add("com.example.a")
|
||||
list.Add("")
|
||||
|
||||
if list.Size() != 1 {
|
||||
t.Errorf("size = %d, want 1", list.Size())
|
||||
}
|
||||
if list.Get(-1) != "" || list.Get(5) != "" {
|
||||
t.Error("out of range access must return an empty string")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user