Files
frp/pkg/config/source/aggregator_test.go
fatedier 0b4f83cd04 pkg/config: use modern Go stdlib for sorting and string operations (#5210)
- slices.SortedFunc + maps.Values + cmp.Compare instead of manual
  map-to-slice collection + sort.Slice (source/aggregator.go)
- strings.CutSuffix instead of HasSuffix+TrimSuffix, and deduplicate
  error handling in BandwidthQuantity.UnmarshalString (types/types.go)
2026-03-06 23:13:29 +08:00

239 lines
6.4 KiB
Go

// Copyright 2026 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package source
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
// mockProxy creates a TCP proxy config for testing
func mockProxy(name string) v1.ProxyConfigurer {
cfg := &v1.TCPProxyConfig{}
cfg.Name = name
cfg.Type = "tcp"
cfg.LocalPort = 8080
cfg.RemotePort = 9090
return cfg
}
// mockVisitor creates a STCP visitor config for testing
func mockVisitor(name string) v1.VisitorConfigurer {
cfg := &v1.STCPVisitorConfig{}
cfg.Name = name
cfg.Type = "stcp"
cfg.ServerName = "test-server"
return cfg
}
func newTestStoreSource(t *testing.T) *StoreSource {
t.Helper()
path := filepath.Join(t.TempDir(), "store.json")
storeSource, err := NewStoreSource(StoreSourceConfig{Path: path})
require.NoError(t, err)
return storeSource
}
func newTestAggregator(t *testing.T, storeSource *StoreSource) *Aggregator {
t.Helper()
configSource := NewConfigSource()
agg := NewAggregator(configSource)
if storeSource != nil {
agg.SetStoreSource(storeSource)
}
return agg
}
func TestNewAggregator_CreatesConfigSourceWhenNil(t *testing.T) {
require := require.New(t)
agg := NewAggregator(nil)
require.NotNil(agg)
require.NotNil(agg.ConfigSource())
require.Nil(agg.StoreSource())
}
func TestNewAggregator_WithoutStore(t *testing.T) {
require := require.New(t)
configSource := NewConfigSource()
agg := NewAggregator(configSource)
require.NotNil(agg)
require.Same(configSource, agg.ConfigSource())
require.Nil(agg.StoreSource())
}
func TestNewAggregator_WithStore(t *testing.T) {
require := require.New(t)
storeSource := newTestStoreSource(t)
configSource := NewConfigSource()
agg := NewAggregator(configSource)
agg.SetStoreSource(storeSource)
require.Same(configSource, agg.ConfigSource())
require.Same(storeSource, agg.StoreSource())
}
func TestAggregator_SetStoreSource_Overwrite(t *testing.T) {
require := require.New(t)
agg := newTestAggregator(t, nil)
first := newTestStoreSource(t)
second := newTestStoreSource(t)
agg.SetStoreSource(first)
require.Same(first, agg.StoreSource())
agg.SetStoreSource(second)
require.Same(second, agg.StoreSource())
agg.SetStoreSource(nil)
require.Nil(agg.StoreSource())
}
func TestAggregator_MergeBySourceOrder(t *testing.T) {
require := require.New(t)
storeSource := newTestStoreSource(t)
agg := newTestAggregator(t, storeSource)
configSource := agg.ConfigSource()
configShared := mockProxy("shared").(*v1.TCPProxyConfig)
configShared.LocalPort = 1111
configOnly := mockProxy("only-in-config").(*v1.TCPProxyConfig)
configOnly.LocalPort = 1112
err := configSource.ReplaceAll([]v1.ProxyConfigurer{configShared, configOnly}, nil)
require.NoError(err)
storeShared := mockProxy("shared").(*v1.TCPProxyConfig)
storeShared.LocalPort = 2222
storeOnly := mockProxy("only-in-store").(*v1.TCPProxyConfig)
storeOnly.LocalPort = 2223
err = storeSource.AddProxy(storeShared)
require.NoError(err)
err = storeSource.AddProxy(storeOnly)
require.NoError(err)
proxies, visitors, err := agg.Load()
require.NoError(err)
require.Len(visitors, 0)
require.Len(proxies, 3)
var sharedProxy *v1.TCPProxyConfig
for _, p := range proxies {
if p.GetBaseConfig().Name == "shared" {
sharedProxy = p.(*v1.TCPProxyConfig)
break
}
}
require.NotNil(sharedProxy)
require.Equal(2222, sharedProxy.LocalPort)
}
func TestAggregator_DisabledEntryIsSourceLocalFilter(t *testing.T) {
require := require.New(t)
storeSource := newTestStoreSource(t)
agg := newTestAggregator(t, storeSource)
configSource := agg.ConfigSource()
lowProxy := mockProxy("shared-proxy").(*v1.TCPProxyConfig)
lowProxy.LocalPort = 1111
err := configSource.ReplaceAll([]v1.ProxyConfigurer{lowProxy}, nil)
require.NoError(err)
disabled := false
highProxy := mockProxy("shared-proxy").(*v1.TCPProxyConfig)
highProxy.LocalPort = 2222
highProxy.Enabled = &disabled
err = storeSource.AddProxy(highProxy)
require.NoError(err)
proxies, visitors, err := agg.Load()
require.NoError(err)
require.Len(proxies, 1)
require.Len(visitors, 0)
proxy := proxies[0].(*v1.TCPProxyConfig)
require.Equal("shared-proxy", proxy.Name)
require.Equal(1111, proxy.LocalPort)
}
func TestAggregator_VisitorMerge(t *testing.T) {
require := require.New(t)
storeSource := newTestStoreSource(t)
agg := newTestAggregator(t, storeSource)
err := agg.ConfigSource().ReplaceAll(nil, []v1.VisitorConfigurer{mockVisitor("visitor1")})
require.NoError(err)
err = storeSource.AddVisitor(mockVisitor("visitor2"))
require.NoError(err)
_, visitors, err := agg.Load()
require.NoError(err)
require.Len(visitors, 2)
}
func TestAggregator_Load_ReturnsSortedByName(t *testing.T) {
require := require.New(t)
agg := newTestAggregator(t, nil)
err := agg.ConfigSource().ReplaceAll(
[]v1.ProxyConfigurer{mockProxy("charlie"), mockProxy("alice"), mockProxy("bob")},
[]v1.VisitorConfigurer{mockVisitor("zulu"), mockVisitor("alpha")},
)
require.NoError(err)
proxies, visitors, err := agg.Load()
require.NoError(err)
require.Len(proxies, 3)
require.Equal("alice", proxies[0].GetBaseConfig().Name)
require.Equal("bob", proxies[1].GetBaseConfig().Name)
require.Equal("charlie", proxies[2].GetBaseConfig().Name)
require.Len(visitors, 2)
require.Equal("alpha", visitors[0].GetBaseConfig().Name)
require.Equal("zulu", visitors[1].GetBaseConfig().Name)
}
func TestAggregator_Load_ReturnsDefensiveCopies(t *testing.T) {
require := require.New(t)
agg := newTestAggregator(t, nil)
err := agg.ConfigSource().ReplaceAll([]v1.ProxyConfigurer{mockProxy("ssh")}, nil)
require.NoError(err)
proxies, _, err := agg.Load()
require.NoError(err)
require.Len(proxies, 1)
require.Equal("ssh", proxies[0].GetBaseConfig().Name)
proxies[0].GetBaseConfig().Name = "alice.ssh"
proxies2, _, err := agg.Load()
require.NoError(err)
require.Len(proxies2, 1)
require.Equal("ssh", proxies2[0].GetBaseConfig().Name)
}