From df24acb4afd5251b6b9650e08fdff3234a40c152 Mon Sep 17 00:00:00 2001 From: yusing Date: Sat, 5 Apr 2025 13:30:54 +0800 Subject: [PATCH] feat(config): add implement file provider validation tests --- agent/pkg/agent/config.go | 11 +- internal/config/config_test.go | 202 +++++++++++++++++++++++++++++++++ internal/utils/validation.go | 8 ++ 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 internal/config/config_test.go diff --git a/agent/pkg/agent/config.go b/agent/pkg/agent/config.go index 29cb270..a0bcbab 100644 --- a/agent/pkg/agent/config.go +++ b/agent/pkg/agent/config.go @@ -15,7 +15,7 @@ import ( "github.com/yusing/go-proxy/agent/pkg/certs" "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/logging" - gphttp "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/pkg" ) @@ -54,6 +54,15 @@ var ( HTTPProxyURLPrefixLen = len(APIEndpointBase + EndpointProxyHTTP) ) +// TestAgentConfig is a helper function to create an AgentConfig for testing purposes. +// Not used in production. +func TestAgentConfig(name string, addr string) *AgentConfig { + return &AgentConfig{ + name: name, + Addr: addr, + } +} + func IsDockerHostAgent(dockerHost string) bool { return strings.HasPrefix(dockerHost, FakeDockerHostPrefix) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..ed6c702 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,202 @@ +package config + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yusing/go-proxy/agent/pkg/agent" + "github.com/yusing/go-proxy/internal/common" + config "github.com/yusing/go-proxy/internal/config/types" + "github.com/yusing/go-proxy/internal/route/provider" + "github.com/yusing/go-proxy/internal/utils" + . "github.com/yusing/go-proxy/internal/utils/testing" + "gopkg.in/yaml.v3" +) + +func TestFileProviderValidate(t *testing.T) { + tests := []struct { + name string + filenames []string + init, cleanup func(filepath string) error + expectedErrorContains string + }{ + { + name: "file not exists", + filenames: []string{"not_exists.yaml"}, + expectedErrorContains: "config_file_exists", + }, + { + name: "file is a directory", + filenames: []string{"testdata"}, + expectedErrorContains: "config_file_exists", + }, + { + name: "same file exists multiple times", + filenames: []string{"test.yml", "test.yml"}, + expectedErrorContains: "unique", + }, + { + name: "file ok", + filenames: []string{"routes.yaml"}, + init: func(filepath string) error { + os.MkdirAll(path.Dir(filepath), 0755) + _, err := os.Create(filepath) + return err + }, + cleanup: func(filepath string) error { + return os.RemoveAll(path.Dir(filepath)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := config.DefaultConfig() + if tt.init != nil { + for _, filename := range tt.filenames { + filepath := path.Join(common.ConfigBasePath, filename) + assert.NoError(t, tt.init(filepath)) + } + } + err := utils.UnmarshalValidateYAML(Must(yaml.Marshal(map[string]any{ + "providers": map[string]any{ + "include": tt.filenames, + }, + })), cfg) + if tt.cleanup != nil { + for _, filename := range tt.filenames { + filepath := path.Join(common.ConfigBasePath, filename) + assert.NoError(t, tt.cleanup(filepath)) + } + } + if tt.expectedErrorContains != "" { + assert.ErrorContains(t, err, tt.expectedErrorContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestLoadRouteProviders(t *testing.T) { + tests := []struct { + name string + providers *config.Providers + expectedError bool + }{ + { + name: "duplicate file provider", + providers: &config.Providers{ + Files: []string{"routes.yaml", "routes.yaml"}, + }, + expectedError: true, + }, + { + name: "duplicate docker provider", + providers: &config.Providers{ + Docker: map[string]string{ + "docker1": "unix:///var/run/docker.sock", + "docker2": "unix:///var/run/docker.sock", + }, + }, + expectedError: true, + }, + { + name: "docker provider with different hosts", + providers: &config.Providers{ + Docker: map[string]string{ + "docker1": "unix:///var/run/docker1.sock", + "docker2": "unix:///var/run/docker2.sock", + }, + }, + expectedError: false, + }, + { + name: "duplicate agent addresses", + providers: &config.Providers{ + Agents: []*agent.AgentConfig{ + {Addr: "192.168.1.100:8080"}, + {Addr: "192.168.1.100:8080"}, + }, + }, + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := utils.Validate(tt.providers) + if tt.expectedError { + assert.ErrorContains(t, err, "unique") + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestProviderNameUniqueness(t *testing.T) { + file := provider.NewFileProvider("routes.yaml") + docker := provider.NewDockerProvider("routes", "unix:///var/run/docker.sock") + agent := provider.NewAgentProvider(agent.TestAgentConfig("routes", "192.168.1.100:8080")) + + assert.True(t, file.String() != docker.String()) + assert.True(t, file.String() != agent.String()) + assert.True(t, docker.String() != agent.String()) +} + +func TestFileProviderNameFromFilename(t *testing.T) { + tests := []struct { + filename string + expectedName string + }{ + {"routes.yaml", "routes"}, + {"service.yml", "service"}, + {"complex-name.yaml", "complex-name"}, + } + + for _, tt := range tests { + t.Run(tt.filename, func(t *testing.T) { + p := provider.NewFileProvider(tt.filename) + assert.Equal(t, tt.expectedName, p.ShortName()) + }) + } +} + +func TestDockerProviderString(t *testing.T) { + tests := []struct { + name string + dockerHost string + expected string + }{ + {"docker1", "unix:///var/run/docker.sock", "docker@docker1"}, + {"host2", "tcp://192.168.1.100:2375", "docker@host2"}, + {"explicit!", "unix:///var/run/docker.sock", "docker@explicit!"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := provider.NewDockerProvider(tt.name, tt.dockerHost) + assert.Equal(t, tt.expected, p.String()) + }) + } +} + +func TestExplicitOnlyProvider(t *testing.T) { + tests := []struct { + name string + expectedFlag bool + }{ + {"docker", false}, + {"explicit!", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := provider.NewDockerProvider(tt.name, "unix:///var/run/docker.sock") + assert.Equal(t, tt.expectedFlag, p.IsExplicitOnly()) + }) + } +} diff --git a/internal/utils/validation.go b/internal/utils/validation.go index f9539bc..d095f29 100644 --- a/internal/utils/validation.go +++ b/internal/utils/validation.go @@ -9,6 +9,14 @@ var validate = validator.New() var ErrValidationError = gperr.New("validation error") +func Validate(v any) gperr.Error { + err := validate.Struct(v) + if err != nil { + return ErrValidationError.With(err) + } + return nil +} + type CustomValidator interface { Validate() gperr.Error }