mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-08 07:51:28 +02:00
gomobile carries only basic types, so the typed constants stay unexported and the exported SplitTunnelMode* ints are what the Java side gets.
152 lines
5.0 KiB
Go
152 lines
5.0 KiB
Go
package android
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestNormalizeSplitTunnelMode(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mode SplitTunnelMode
|
|
want SplitTunnelMode
|
|
}{
|
|
{name: "exclude is kept", mode: modeExclude, want: modeExclude},
|
|
{name: "include is kept", mode: modeInclude, want: modeInclude},
|
|
{name: "off is kept", mode: modeOff, want: modeOff},
|
|
{name: "a mode from a newer build falls back to off", mode: SplitTunnelMode(7), want: modeOff},
|
|
{name: "a negative mode falls back to off", mode: SplitTunnelMode(-1), want: modeOff},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := normalizeSplitTunnelMode(tt.mode); got != tt.want {
|
|
t.Errorf("normalizeSplitTunnelMode(%d) = %d, want %d", tt.mode, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The constants the Android side reads must stay the values the store writes:
|
|
// gomobile carries the ints below, not the typed constants they mirror.
|
|
func TestSplitTunnelModeConstantsMirrorTheTypedOnes(t *testing.T) {
|
|
if SplitTunnelModeOff != int(modeOff) {
|
|
t.Errorf("off = %d, want %d", SplitTunnelModeOff, modeOff)
|
|
}
|
|
if SplitTunnelModeExclude != int(modeExclude) {
|
|
t.Errorf("exclude = %d, want %d", SplitTunnelModeExclude, modeExclude)
|
|
}
|
|
if SplitTunnelModeInclude != int(modeInclude) {
|
|
t.Errorf("include = %d, want %d", SplitTunnelModeInclude, modeInclude)
|
|
}
|
|
}
|
|
|
|
func TestSettingsFromSection(t *testing.T) {
|
|
got := settingsFromSection(splitTunnelSection{
|
|
Mode: modeExclude,
|
|
Excluded: []string{"com.example.a", "com.example.b"},
|
|
Included: []string{"com.example.c"},
|
|
})
|
|
|
|
if got.Mode != SplitTunnelModeExclude {
|
|
t.Errorf("mode = %d, want %d", 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 = %d, want %d", 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))
|
|
}
|
|
}
|
|
|
|
// The section is what the profile's preference file holds, so the mode has to
|
|
// survive a JSON round trip as the number the constants name.
|
|
func TestSectionEncodesTheModeAsItsNumber(t *testing.T) {
|
|
raw, err := json.Marshal(sectionFromSettings(&SplitTunnelSettings{Mode: SplitTunnelModeInclude}))
|
|
if err != nil {
|
|
t.Fatalf("marshal section: %v", err)
|
|
}
|
|
|
|
var back splitTunnelSection
|
|
if err := json.Unmarshal(raw, &back); err != nil {
|
|
t.Fatalf("unmarshal section: %v", err)
|
|
}
|
|
if back.Mode != modeInclude {
|
|
t.Errorf("mode = %d, want %d, from %s", back.Mode, modeInclude, raw)
|
|
}
|
|
}
|
|
|
|
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 = %d, want %d", 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))
|
|
}
|
|
}
|
|
|
|
// A mode the Java side never sets, such as one left by a newer build, must not
|
|
// reach the stored section either.
|
|
func TestSectionFromSettingsNormalizesAnUnknownMode(t *testing.T) {
|
|
section := sectionFromSettings(&SplitTunnelSettings{Mode: 7})
|
|
|
|
if section.Mode != modeOff {
|
|
t.Errorf("mode = %d, want %d", section.Mode, modeOff)
|
|
}
|
|
}
|
|
|
|
func TestSectionFromNilSettings(t *testing.T) {
|
|
section := sectionFromSettings(nil)
|
|
|
|
if section.Mode != modeOff {
|
|
t.Errorf("mode = %d, want %d", section.Mode, modeOff)
|
|
}
|
|
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")
|
|
}
|
|
}
|