feat: add persistent proxy profile management
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"proxyctl/internal/app"
|
||||
"proxyctl/internal/profile"
|
||||
)
|
||||
|
||||
func TestProfileValidationAcceptsSupportedURLsAndNormalizesEveryBypassKind(t *testing.T) {
|
||||
application, err := app.New(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{
|
||||
Name: "mixed",
|
||||
Mode: profile.AdvancedMode,
|
||||
HTTPURL: "http://proxy.example:80",
|
||||
HTTPSURL: "https://user:secret@proxy.example:443",
|
||||
AllURL: "socks5h://[2001:db8::5]:1080",
|
||||
Bypass: []string{
|
||||
"LOCALHOST, Example.COM.",
|
||||
".Internal.Example",
|
||||
"192.0.2.1",
|
||||
"2001:0db8::1",
|
||||
"10.2.3.4/8",
|
||||
"2001:db8:abcd::5/48",
|
||||
"localhost",
|
||||
},
|
||||
}})
|
||||
if result.Err != nil {
|
||||
t.Fatalf("create validated Profile: %v", result.Err)
|
||||
}
|
||||
want := []string{"localhost", "example.com", ".internal.example", "192.0.2.1", "2001:db8::1", "10.0.0.0/8", "2001:db8:abcd::/48"}
|
||||
if got := result.Snapshot.Profiles[0].Bypass; !equalStrings(got, want) {
|
||||
t.Fatalf("normalized Bypass = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfileValidationRejectsMalformedURLsAndBypassWithActionableErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
profile profile.Profile
|
||||
want string
|
||||
}{
|
||||
{"unsupported scheme", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "ftp://user:secret@proxy.example:21"}, "http, https, socks5, or socks5h"},
|
||||
{"missing host", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "http://:8080"}, "host"},
|
||||
{"missing port", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "http://proxy.example"}, "port"},
|
||||
{"invalid port", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "http://proxy.example:70000"}, "between 1 and 65535"},
|
||||
{"path", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "http://proxy.example:8080/path"}, "path"},
|
||||
{"invalid IPv4 host", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "http://999.1.2.3:8080"}, "invalid host"},
|
||||
{"invalid bypass", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "http://proxy.example:8080", Bypass: []string{"*"}}, "hostname, domain suffix, IP address, or CIDR"},
|
||||
{"invalid IPv4 bypass", profile.Profile{Name: "bad", Mode: profile.SingleMode, SingleURL: "http://proxy.example:8080", Bypass: []string{"999.1.2.3"}}, "hostname, domain suffix, IP address, or CIDR"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
application, err := app.New(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: tt.profile})
|
||||
if result.Err == nil || !strings.Contains(result.Err.Error(), tt.want) {
|
||||
t.Fatalf("error = %v, want actionable text containing %q", result.Err, tt.want)
|
||||
}
|
||||
if strings.Contains(result.Err.Error(), "secret") {
|
||||
t.Fatalf("validation error exposed proxy password: %v", result.Err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user