mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			348 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package mapper
 | |
| 
 | |
| import (
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/juanfont/headscale/hscontrol/state"
 | |
| 	"github.com/juanfont/headscale/hscontrol/types"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	"tailscale.com/tailcfg"
 | |
| )
 | |
| 
 | |
| func TestMapResponseBuilder_Basic(t *testing.T) {
 | |
| 	cfg := &types.Config{
 | |
| 		BaseDomain: "example.com",
 | |
| 		LogTail: types.LogTailConfig{
 | |
| 			Enabled: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID)
 | |
| 
 | |
| 	// Test basic builder creation
 | |
| 	assert.NotNil(t, builder)
 | |
| 	assert.Equal(t, nodeID, builder.nodeID)
 | |
| 	assert.NotNil(t, builder.resp)
 | |
| 	assert.False(t, builder.resp.KeepAlive)
 | |
| 	assert.NotNil(t, builder.resp.ControlTime)
 | |
| 	assert.WithinDuration(t, time.Now(), *builder.resp.ControlTime, time.Second)
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_WithCapabilityVersion(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 	capVer := tailcfg.CapabilityVersion(42)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithCapabilityVersion(capVer)
 | |
| 
 | |
| 	assert.Equal(t, capVer, builder.capVer)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_WithDomain(t *testing.T) {
 | |
| 	domain := "test.example.com"
 | |
| 	cfg := &types.Config{
 | |
| 		ServerURL:  "https://test.example.com",
 | |
| 		BaseDomain: domain,
 | |
| 	}
 | |
| 
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithDomain()
 | |
| 
 | |
| 	assert.Equal(t, domain, builder.resp.Domain)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_WithCollectServicesDisabled(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithCollectServicesDisabled()
 | |
| 
 | |
| 	value, isSet := builder.resp.CollectServices.Get()
 | |
| 	assert.True(t, isSet)
 | |
| 	assert.False(t, value)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_WithDebugConfig(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name           string
 | |
| 		logTailEnabled bool
 | |
| 		expected       bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:           "LogTail enabled",
 | |
| 			logTailEnabled: true,
 | |
| 			expected:       false, // DisableLogTail should be false when LogTail is enabled
 | |
| 		},
 | |
| 		{
 | |
| 			name:           "LogTail disabled",
 | |
| 			logTailEnabled: false,
 | |
| 			expected:       true, // DisableLogTail should be true when LogTail is disabled
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			cfg := &types.Config{
 | |
| 				LogTail: types.LogTailConfig{
 | |
| 					Enabled: tt.logTailEnabled,
 | |
| 				},
 | |
| 			}
 | |
| 			mockState := &state.State{}
 | |
| 			m := &mapper{
 | |
| 				cfg:   cfg,
 | |
| 				state: mockState,
 | |
| 			}
 | |
| 
 | |
| 			nodeID := types.NodeID(1)
 | |
| 
 | |
| 			builder := m.NewMapResponseBuilder(nodeID).
 | |
| 				WithDebugConfig()
 | |
| 
 | |
| 			require.NotNil(t, builder.resp.Debug)
 | |
| 			assert.Equal(t, tt.expected, builder.resp.Debug.DisableLogTail)
 | |
| 			assert.False(t, builder.hasErrors())
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_WithPeerChangedPatch(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 	changes := []*tailcfg.PeerChange{
 | |
| 		{
 | |
| 			NodeID:     123,
 | |
| 			DERPRegion: 1,
 | |
| 		},
 | |
| 		{
 | |
| 			NodeID:     456,
 | |
| 			DERPRegion: 2,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithPeerChangedPatch(changes)
 | |
| 
 | |
| 	assert.Equal(t, changes, builder.resp.PeersChangedPatch)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_WithPeersRemoved(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 	removedID1 := types.NodeID(123)
 | |
| 	removedID2 := types.NodeID(456)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithPeersRemoved(removedID1, removedID2)
 | |
| 
 | |
| 	expected := []tailcfg.NodeID{
 | |
| 		removedID1.NodeID(),
 | |
| 		removedID2.NodeID(),
 | |
| 	}
 | |
| 	assert.Equal(t, expected, builder.resp.PeersRemoved)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_ErrorHandling(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 
 | |
| 	// Simulate an error in the builder
 | |
| 	builder := m.NewMapResponseBuilder(nodeID)
 | |
| 	builder.addError(assert.AnError)
 | |
| 
 | |
| 	// All subsequent calls should continue to work and accumulate errors
 | |
| 	result := builder.
 | |
| 		WithDomain().
 | |
| 		WithCollectServicesDisabled().
 | |
| 		WithDebugConfig()
 | |
| 
 | |
| 	assert.True(t, result.hasErrors())
 | |
| 	assert.Len(t, result.errs, 1)
 | |
| 	assert.Equal(t, assert.AnError, result.errs[0])
 | |
| 
 | |
| 	// Build should return the error
 | |
| 	data, err := result.Build()
 | |
| 	assert.Nil(t, data)
 | |
| 	assert.Error(t, err)
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_ChainedCalls(t *testing.T) {
 | |
| 	domain := "chained.example.com"
 | |
| 	cfg := &types.Config{
 | |
| 		ServerURL:  "https://chained.example.com",
 | |
| 		BaseDomain: domain,
 | |
| 		LogTail: types.LogTailConfig{
 | |
| 			Enabled: false,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 	capVer := tailcfg.CapabilityVersion(99)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithCapabilityVersion(capVer).
 | |
| 		WithDomain().
 | |
| 		WithCollectServicesDisabled().
 | |
| 		WithDebugConfig()
 | |
| 
 | |
| 	// Verify all fields are set correctly
 | |
| 	assert.Equal(t, capVer, builder.capVer)
 | |
| 	assert.Equal(t, domain, builder.resp.Domain)
 | |
| 	value, isSet := builder.resp.CollectServices.Get()
 | |
| 	assert.True(t, isSet)
 | |
| 	assert.False(t, value)
 | |
| 	assert.NotNil(t, builder.resp.Debug)
 | |
| 	assert.True(t, builder.resp.Debug.DisableLogTail)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_MultipleWithPeersRemoved(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 	removedID1 := types.NodeID(100)
 | |
| 	removedID2 := types.NodeID(200)
 | |
| 
 | |
| 	// Test calling WithPeersRemoved multiple times
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithPeersRemoved(removedID1).
 | |
| 		WithPeersRemoved(removedID2)
 | |
| 
 | |
| 	// Second call should overwrite the first
 | |
| 	expected := []tailcfg.NodeID{removedID2.NodeID()}
 | |
| 	assert.Equal(t, expected, builder.resp.PeersRemoved)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_EmptyPeerChangedPatch(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithPeerChangedPatch([]*tailcfg.PeerChange{})
 | |
| 
 | |
| 	assert.Empty(t, builder.resp.PeersChangedPatch)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_NilPeerChangedPatch(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 
 | |
| 	builder := m.NewMapResponseBuilder(nodeID).
 | |
| 		WithPeerChangedPatch(nil)
 | |
| 
 | |
| 	assert.Nil(t, builder.resp.PeersChangedPatch)
 | |
| 	assert.False(t, builder.hasErrors())
 | |
| }
 | |
| 
 | |
| func TestMapResponseBuilder_MultipleErrors(t *testing.T) {
 | |
| 	cfg := &types.Config{}
 | |
| 	mockState := &state.State{}
 | |
| 	m := &mapper{
 | |
| 		cfg:   cfg,
 | |
| 		state: mockState,
 | |
| 	}
 | |
| 
 | |
| 	nodeID := types.NodeID(1)
 | |
| 
 | |
| 	// Create a builder and add multiple errors
 | |
| 	builder := m.NewMapResponseBuilder(nodeID)
 | |
| 	builder.addError(assert.AnError)
 | |
| 	builder.addError(assert.AnError)
 | |
| 	builder.addError(nil) // This should be ignored
 | |
| 
 | |
| 	// All subsequent calls should continue to work
 | |
| 	result := builder.
 | |
| 		WithDomain().
 | |
| 		WithCollectServicesDisabled()
 | |
| 
 | |
| 	assert.True(t, result.hasErrors())
 | |
| 	assert.Len(t, result.errs, 2) // nil error should be ignored
 | |
| 
 | |
| 	// Build should return a multierr
 | |
| 	data, err := result.Build()
 | |
| 	assert.Nil(t, data)
 | |
| 	assert.Error(t, err)
 | |
| 
 | |
| 	// The error should contain information about multiple errors
 | |
| 	assert.Contains(t, err.Error(), "multiple errors")
 | |
| }
 |