|
|
|
|
@ -8,6 +8,7 @@ import (
|
|
|
|
|
|
|
|
|
|
"tailscale.com/health"
|
|
|
|
|
"tailscale.com/types/empty"
|
|
|
|
|
"tailscale.com/types/key"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestNotifyString(t *testing.T) {
|
|
|
|
|
@ -40,3 +41,286 @@ func TestNotifyString(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== State Tests =====
|
|
|
|
|
|
|
|
|
|
func TestState_String(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
state State
|
|
|
|
|
expected string
|
|
|
|
|
}{
|
|
|
|
|
{NoState, "NoState"},
|
|
|
|
|
{InUseOtherUser, "InUseOtherUser"},
|
|
|
|
|
{NeedsLogin, "NeedsLogin"},
|
|
|
|
|
{NeedsMachineAuth, "NeedsMachineAuth"},
|
|
|
|
|
{Stopped, "Stopped"},
|
|
|
|
|
{Starting, "Starting"},
|
|
|
|
|
{Running, "Running"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.expected, func(t *testing.T) {
|
|
|
|
|
got := tt.state.String()
|
|
|
|
|
if got != tt.expected {
|
|
|
|
|
t.Errorf("State(%d).String() = %q, want %q", tt.state, got, tt.expected)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestState_Values(t *testing.T) {
|
|
|
|
|
// Test that all state values are distinct
|
|
|
|
|
states := []State{NoState, InUseOtherUser, NeedsLogin, NeedsMachineAuth, Stopped, Starting, Running}
|
|
|
|
|
seen := make(map[State]bool)
|
|
|
|
|
|
|
|
|
|
for _, s := range states {
|
|
|
|
|
if seen[s] {
|
|
|
|
|
t.Errorf("duplicate state value: %v", s)
|
|
|
|
|
}
|
|
|
|
|
seen[s] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestState_Transitions(t *testing.T) {
|
|
|
|
|
// Test common state transitions make sense
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
from State
|
|
|
|
|
to State
|
|
|
|
|
valid bool
|
|
|
|
|
}{
|
|
|
|
|
{"stopped_to_starting", Stopped, Starting, true},
|
|
|
|
|
{"starting_to_running", Starting, Running, true},
|
|
|
|
|
{"running_to_stopped", Running, Stopped, true},
|
|
|
|
|
{"needs_login_to_starting", NeedsLogin, Starting, true},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
// Just verify states are different (basic sanity)
|
|
|
|
|
if tt.from == tt.to {
|
|
|
|
|
t.Errorf("transition from %v to %v: states are the same", tt.from, tt.to)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== EngineStatus Tests =====
|
|
|
|
|
|
|
|
|
|
func TestEngineStatus(t *testing.T) {
|
|
|
|
|
es := EngineStatus{
|
|
|
|
|
RBytes: 1000,
|
|
|
|
|
WBytes: 2000,
|
|
|
|
|
NumLive: 5,
|
|
|
|
|
LiveDERPs: 2,
|
|
|
|
|
LivePeers: make(map[key.NodePublic]ipnstate.PeerStatusLite),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if es.RBytes != 1000 {
|
|
|
|
|
t.Errorf("RBytes = %d, want 1000", es.RBytes)
|
|
|
|
|
}
|
|
|
|
|
if es.WBytes != 2000 {
|
|
|
|
|
t.Errorf("WBytes = %d, want 2000", es.WBytes)
|
|
|
|
|
}
|
|
|
|
|
if es.NumLive != 5 {
|
|
|
|
|
t.Errorf("NumLive = %d, want 5", es.NumLive)
|
|
|
|
|
}
|
|
|
|
|
if es.LiveDERPs != 2 {
|
|
|
|
|
t.Errorf("LiveDERPs = %d, want 2", es.LiveDERPs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEngineStatus_ZeroValues(t *testing.T) {
|
|
|
|
|
var es EngineStatus
|
|
|
|
|
if es.RBytes != 0 {
|
|
|
|
|
t.Errorf("zero EngineStatus.RBytes = %d, want 0", es.RBytes)
|
|
|
|
|
}
|
|
|
|
|
if es.WBytes != 0 {
|
|
|
|
|
t.Errorf("zero EngineStatus.WBytes = %d, want 0", es.WBytes)
|
|
|
|
|
}
|
|
|
|
|
if es.NumLive != 0 {
|
|
|
|
|
t.Errorf("zero EngineStatus.NumLive = %d, want 0", es.NumLive)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== NotifyWatchOpt Tests =====
|
|
|
|
|
|
|
|
|
|
func TestNotifyWatchOpt_Constants(t *testing.T) {
|
|
|
|
|
// Verify all constants are distinct powers of 2 (can be OR'd together)
|
|
|
|
|
opts := []NotifyWatchOpt{
|
|
|
|
|
NotifyWatchEngineUpdates,
|
|
|
|
|
NotifyInitialState,
|
|
|
|
|
NotifyInitialPrefs,
|
|
|
|
|
NotifyInitialNetMap,
|
|
|
|
|
NotifyNoPrivateKeys,
|
|
|
|
|
NotifyInitialDriveShares,
|
|
|
|
|
NotifyInitialOutgoingFiles,
|
|
|
|
|
NotifyInitialHealthState,
|
|
|
|
|
NotifyRateLimit,
|
|
|
|
|
NotifyHealthActions,
|
|
|
|
|
NotifyInitialSuggestedExitNode,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seen := make(map[NotifyWatchOpt]bool)
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
if seen[opt] {
|
|
|
|
|
t.Errorf("duplicate NotifyWatchOpt value: %d", opt)
|
|
|
|
|
}
|
|
|
|
|
seen[opt] = true
|
|
|
|
|
|
|
|
|
|
// Verify it's a power of 2 (single bit set)
|
|
|
|
|
if opt != 0 && (opt&(opt-1)) != 0 {
|
|
|
|
|
t.Errorf("NotifyWatchOpt %d is not a power of 2", opt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNotifyWatchOpt_Combinations(t *testing.T) {
|
|
|
|
|
// Test combining multiple options
|
|
|
|
|
combined := NotifyWatchEngineUpdates | NotifyInitialState | NotifyInitialPrefs
|
|
|
|
|
|
|
|
|
|
// Check that all bits are set
|
|
|
|
|
if combined&NotifyWatchEngineUpdates == 0 {
|
|
|
|
|
t.Error("combined should include NotifyWatchEngineUpdates")
|
|
|
|
|
}
|
|
|
|
|
if combined&NotifyInitialState == 0 {
|
|
|
|
|
t.Error("combined should include NotifyInitialState")
|
|
|
|
|
}
|
|
|
|
|
if combined&NotifyInitialPrefs == 0 {
|
|
|
|
|
t.Error("combined should include NotifyInitialPrefs")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that other bits are not set
|
|
|
|
|
if combined&NotifyInitialNetMap != 0 {
|
|
|
|
|
t.Error("combined should not include NotifyInitialNetMap")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNotifyWatchOpt_BitwiseOperations(t *testing.T) {
|
|
|
|
|
var opts NotifyWatchOpt
|
|
|
|
|
|
|
|
|
|
// Start with nothing
|
|
|
|
|
if opts != 0 {
|
|
|
|
|
t.Errorf("initial opts = %d, want 0", opts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add NotifyWatchEngineUpdates
|
|
|
|
|
opts |= NotifyWatchEngineUpdates
|
|
|
|
|
if opts&NotifyWatchEngineUpdates == 0 {
|
|
|
|
|
t.Error("should have NotifyWatchEngineUpdates set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add NotifyInitialState
|
|
|
|
|
opts |= NotifyInitialState
|
|
|
|
|
if opts&NotifyInitialState == 0 {
|
|
|
|
|
t.Error("should have NotifyInitialState set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Both should still be set
|
|
|
|
|
if opts&NotifyWatchEngineUpdates == 0 {
|
|
|
|
|
t.Error("should still have NotifyWatchEngineUpdates set")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== GoogleIDTokenType Tests =====
|
|
|
|
|
|
|
|
|
|
func TestGoogleIDTokenType(t *testing.T) {
|
|
|
|
|
expected := "ts_android_google_login"
|
|
|
|
|
if GoogleIDTokenType != expected {
|
|
|
|
|
t.Errorf("GoogleIDTokenType = %q, want %q", GoogleIDTokenType, expected)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== Notify Field Tests =====
|
|
|
|
|
|
|
|
|
|
func TestNotify_WithVersion(t *testing.T) {
|
|
|
|
|
n := Notify{Version: "1.2.3"}
|
|
|
|
|
s := n.String()
|
|
|
|
|
if s != "Notify{Version=\"1.2.3\"}" {
|
|
|
|
|
t.Errorf("Notify with version: got %q", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNotify_WithState(t *testing.T) {
|
|
|
|
|
state := Running
|
|
|
|
|
n := Notify{State: &state}
|
|
|
|
|
s := n.String()
|
|
|
|
|
if s == "Notify{}" {
|
|
|
|
|
t.Error("Notify with State should not be empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNotify_WithErr(t *testing.T) {
|
|
|
|
|
errMsg := "test error"
|
|
|
|
|
n := Notify{ErrMessage: &errMsg}
|
|
|
|
|
s := n.String()
|
|
|
|
|
if s == "Notify{}" {
|
|
|
|
|
t.Error("Notify with ErrMessage should not be empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNotify_MultipleFields(t *testing.T) {
|
|
|
|
|
state := Running
|
|
|
|
|
errMsg := "error"
|
|
|
|
|
n := Notify{
|
|
|
|
|
Version: "1.0.0",
|
|
|
|
|
State: &state,
|
|
|
|
|
ErrMessage: &errMsg,
|
|
|
|
|
LoginFinished: &empty.Message{},
|
|
|
|
|
}
|
|
|
|
|
s := n.String()
|
|
|
|
|
|
|
|
|
|
// Should contain multiple indicators
|
|
|
|
|
if s == "Notify{}" {
|
|
|
|
|
t.Error("Notify with multiple fields should have non-empty string")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== Edge Cases =====
|
|
|
|
|
|
|
|
|
|
func TestState_InvalidValue(t *testing.T) {
|
|
|
|
|
// Test that an invalid state value doesn't panic
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
t.Errorf("State.String() panicked with invalid value: %v", r)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
var s State = 999
|
|
|
|
|
_ = s.String() // Should not panic
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNotifyWatchOpt_Zero(t *testing.T) {
|
|
|
|
|
var opt NotifyWatchOpt
|
|
|
|
|
if opt != 0 {
|
|
|
|
|
t.Errorf("zero NotifyWatchOpt = %d, want 0", opt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNotifyWatchOpt_AllBits(t *testing.T) {
|
|
|
|
|
// Combine all options
|
|
|
|
|
all := NotifyWatchEngineUpdates |
|
|
|
|
|
NotifyInitialState |
|
|
|
|
|
NotifyInitialPrefs |
|
|
|
|
|
NotifyInitialNetMap |
|
|
|
|
|
NotifyNoPrivateKeys |
|
|
|
|
|
NotifyInitialDriveShares |
|
|
|
|
|
NotifyInitialOutgoingFiles |
|
|
|
|
|
NotifyInitialHealthState |
|
|
|
|
|
NotifyRateLimit |
|
|
|
|
|
NotifyHealthActions |
|
|
|
|
|
NotifyInitialSuggestedExitNode
|
|
|
|
|
|
|
|
|
|
// Should have multiple bits set
|
|
|
|
|
if all == 0 {
|
|
|
|
|
t.Error("combining all NotifyWatchOpt should be non-zero")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check each individual bit is present
|
|
|
|
|
if all&NotifyWatchEngineUpdates == 0 {
|
|
|
|
|
t.Error("all should include NotifyWatchEngineUpdates")
|
|
|
|
|
}
|
|
|
|
|
if all&NotifyInitialSuggestedExitNode == 0 {
|
|
|
|
|
t.Error("all should include NotifyInitialSuggestedExitNode")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|