1391 lines
35 KiB
Go
1391 lines
35 KiB
Go
//go:build linux || freebsd
|
|
// +build linux freebsd
|
|
|
|
package protocol_test
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"codeberg.org/petrbalvin/goget/internal/core"
|
|
"codeberg.org/petrbalvin/goget/internal/protocol"
|
|
)
|
|
|
|
// ---------- Mock Protocol Implementation ----------
|
|
|
|
type mockProtocol struct {
|
|
*protocol.BaseProtocol
|
|
canHandleFn func(u *url.URL) bool
|
|
}
|
|
|
|
func (m *mockProtocol) CanHandle(u *url.URL) bool {
|
|
if m.canHandleFn != nil {
|
|
return m.canHandleFn(u)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (m *mockProtocol) Download(ctx context.Context, req *core.DownloadRequest) (*core.DownloadResult, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *mockProtocol) SupportsParallel() bool {
|
|
return false
|
|
}
|
|
|
|
func (m *mockProtocol) SupportsRecursive() bool {
|
|
return false
|
|
}
|
|
|
|
// ---------- ProtocolInfo Tests ----------
|
|
|
|
func TestProtocolInfo(t *testing.T) {
|
|
t.Run("creates with all fields", func(t *testing.T) {
|
|
info := protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
Version: "1.1",
|
|
Operations: []string{"download", "upload"},
|
|
Features: []string{"resume", "compression", "range"},
|
|
DefaultPort: 80,
|
|
}
|
|
if info.Name != "HTTP" {
|
|
t.Errorf("Name = %q, want %q", info.Name, "HTTP")
|
|
}
|
|
if info.Scheme != "http" {
|
|
t.Errorf("Scheme = %q, want %q", info.Scheme, "http")
|
|
}
|
|
if info.Version != "1.1" {
|
|
t.Errorf("Version = %q, want %q", info.Version, "1.1")
|
|
}
|
|
if len(info.Operations) != 2 {
|
|
t.Errorf("len(Operations) = %d, want %d", len(info.Operations), 2)
|
|
}
|
|
if len(info.Features) != 3 {
|
|
t.Errorf("len(Features) = %d, want %d", len(info.Features), 3)
|
|
}
|
|
if info.DefaultPort != 80 {
|
|
t.Errorf("DefaultPort = %d, want %d", info.DefaultPort, 80)
|
|
}
|
|
})
|
|
|
|
t.Run("empty ProtocolInfo", func(t *testing.T) {
|
|
info := protocol.ProtocolInfo{}
|
|
if info.Name != "" {
|
|
t.Errorf("Name = %q, want %q", info.Name, "")
|
|
}
|
|
if info.Scheme != "" {
|
|
t.Errorf("Scheme = %q, want %q", info.Scheme, "")
|
|
}
|
|
if info.DefaultPort != 0 {
|
|
t.Errorf("DefaultPort = %d, want %d", info.DefaultPort, 0)
|
|
}
|
|
if len(info.Operations) != 0 {
|
|
t.Errorf("len(Operations) = %d, want %d", len(info.Operations), 0)
|
|
}
|
|
if len(info.Features) != 0 {
|
|
t.Errorf("len(Features) = %d, want %d", len(info.Features), 0)
|
|
}
|
|
})
|
|
|
|
t.Run("with features subset", func(t *testing.T) {
|
|
info := protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
Features: []string{"upload"},
|
|
}
|
|
if info.Name != "FTP" {
|
|
t.Errorf("Name = %q, want %q", info.Name, "FTP")
|
|
}
|
|
if len(info.Features) != 1 || info.Features[0] != "upload" {
|
|
t.Errorf("Features = %v, want [upload]", info.Features)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- NewBaseProtocol Tests ----------
|
|
|
|
func TestNewBaseProtocol(t *testing.T) {
|
|
t.Run("creates with valid info", func(t *testing.T) {
|
|
info := protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
Version: "1.1",
|
|
Features: []string{"resume", "compression"},
|
|
}
|
|
bp := protocol.NewBaseProtocol(info)
|
|
if bp == nil {
|
|
t.Fatal("NewBaseProtocol returned nil")
|
|
}
|
|
})
|
|
|
|
t.Run("creates with empty info", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{})
|
|
if bp == nil {
|
|
t.Fatal("NewBaseProtocol with empty info returned nil")
|
|
}
|
|
})
|
|
|
|
t.Run("preserves info fields", func(t *testing.T) {
|
|
info := protocol.ProtocolInfo{
|
|
Name: "SFTP",
|
|
Scheme: "sftp",
|
|
Version: "3.0",
|
|
DefaultPort: 22,
|
|
Operations: []string{"download", "upload"},
|
|
Features: []string{"resume", "compression", "range", "upload"},
|
|
}
|
|
bp := protocol.NewBaseProtocol(info)
|
|
if bp.Scheme() != info.Scheme {
|
|
t.Errorf("Scheme() = %q, want %q", bp.Scheme(), info.Scheme)
|
|
}
|
|
if bp.Name() != info.Name {
|
|
t.Errorf("Name() = %q, want %q", bp.Name(), info.Name)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- BaseProtocol Scheme Tests ----------
|
|
|
|
func TestBaseProtocolScheme(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scheme string
|
|
expected string
|
|
}{
|
|
{"http scheme", "http", "http"},
|
|
{"https scheme", "https", "https"},
|
|
{"ftp scheme", "ftp", "ftp"},
|
|
{"sftp scheme", "sftp", "sftp"},
|
|
{"empty scheme", "", ""},
|
|
{"custom scheme", "custom", "custom"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{Scheme: tt.scheme})
|
|
got := bp.Scheme()
|
|
if got != tt.expected {
|
|
t.Errorf("Scheme() = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---------- BaseProtocol Name Tests ----------
|
|
|
|
func TestBaseProtocolName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
protName string
|
|
expected string
|
|
}{
|
|
{"HTTP name", "HTTP", "HTTP"},
|
|
{"FTP name", "FTP", "FTP"},
|
|
{"empty name", "", ""},
|
|
{"with version", "HTTP/1.1", "HTTP/1.1"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{Name: tt.protName})
|
|
got := bp.Name()
|
|
if got != tt.expected {
|
|
t.Errorf("Name() = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---------- BaseProtocol Capabilities Tests ----------
|
|
|
|
func TestBaseProtocolCapabilities(t *testing.T) {
|
|
t.Run("returns features slice", func(t *testing.T) {
|
|
features := []string{"resume", "compression", "range"}
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{Features: features})
|
|
caps := bp.Capabilities()
|
|
if len(caps) != len(features) {
|
|
t.Errorf("len(Capabilities()) = %d, want %d", len(caps), len(features))
|
|
}
|
|
for i, f := range features {
|
|
if caps[i] != f {
|
|
t.Errorf("Capabilities()[%d] = %q, want %q", i, caps[i], f)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("returns empty slice for no features", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{})
|
|
caps := bp.Capabilities()
|
|
if len(caps) != 0 {
|
|
t.Errorf("len(Capabilities()) = %d, want %d", len(caps), 0)
|
|
}
|
|
})
|
|
|
|
t.Run("returns slice reflecting ProtocolInfo.Features", func(t *testing.T) {
|
|
features := []string{"resume", "compression"}
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{Features: features})
|
|
caps := bp.Capabilities()
|
|
if len(caps) != len(features) {
|
|
t.Fatalf("len(Capabilities()) = %d, want %d", len(caps), len(features))
|
|
}
|
|
for i, f := range features {
|
|
if caps[i] != f {
|
|
t.Errorf("Capabilities()[%d] = %q, want %q", i, caps[i], f)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- BaseProtocol SupportsResume Tests ----------
|
|
|
|
func TestBaseProtocolSupportsResume(t *testing.T) {
|
|
t.Run("with resume feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume"},
|
|
})
|
|
if !bp.SupportsResume() {
|
|
t.Error("SupportsResume() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("without resume feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"compression", "range"},
|
|
})
|
|
if bp.SupportsResume() {
|
|
t.Error("SupportsResume() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("with empty features", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{})
|
|
if bp.SupportsResume() {
|
|
t.Error("SupportsResume() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("resume is first feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume", "compression", "range"},
|
|
})
|
|
if !bp.SupportsResume() {
|
|
t.Error("SupportsResume() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("resume is last feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"compression", "range", "upload", "resume"},
|
|
})
|
|
if !bp.SupportsResume() {
|
|
t.Error("SupportsResume() = false, want true")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- BaseProtocol SupportsCompression Tests ----------
|
|
|
|
func TestBaseProtocolSupportsCompression(t *testing.T) {
|
|
t.Run("with compression feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"compression"},
|
|
})
|
|
if !bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("without compression feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume", "range"},
|
|
})
|
|
if bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("with empty features", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{})
|
|
if bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = true, want false")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- BaseProtocol SupportsUpload Tests ----------
|
|
|
|
func TestBaseProtocolSupportsUpload(t *testing.T) {
|
|
t.Run("with upload feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"upload"},
|
|
})
|
|
if !bp.SupportsUpload() {
|
|
t.Error("SupportsUpload() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("without upload feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume", "compression"},
|
|
})
|
|
if bp.SupportsUpload() {
|
|
t.Error("SupportsUpload() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("with empty features", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{})
|
|
if bp.SupportsUpload() {
|
|
t.Error("SupportsUpload() = true, want false")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- BaseProtocol SupportsRange Tests ----------
|
|
|
|
func TestBaseProtocolSupportsRange(t *testing.T) {
|
|
t.Run("with range feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"range"},
|
|
})
|
|
if !bp.SupportsRange() {
|
|
t.Error("SupportsRange() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("without range feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume", "compression"},
|
|
})
|
|
if bp.SupportsRange() {
|
|
t.Error("SupportsRange() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("with empty features", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{})
|
|
if bp.SupportsRange() {
|
|
t.Error("SupportsRange() = true, want false")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- ValidateURL Tests ----------
|
|
|
|
func TestValidateURL(t *testing.T) {
|
|
t.Run("valid URL", func(t *testing.T) {
|
|
u, _ := url.Parse("http://example.com/file.txt")
|
|
err := protocol.ValidateURL(u, "http")
|
|
if err != nil {
|
|
t.Errorf("ValidateURL() error = %v, want nil", err)
|
|
}
|
|
})
|
|
|
|
t.Run("nil URL", func(t *testing.T) {
|
|
err := protocol.ValidateURL(nil, "http")
|
|
if err == nil {
|
|
t.Fatal("ValidateURL(nil) error = nil, want error")
|
|
}
|
|
gogetErr, ok := err.(*core.GogetError)
|
|
if !ok {
|
|
t.Fatalf("error type = %T, want *core.GogetError", err)
|
|
}
|
|
if gogetErr.Message != "URL is nil" {
|
|
t.Errorf("Message = %q, want %q", gogetErr.Message, "URL is nil")
|
|
}
|
|
})
|
|
|
|
t.Run("wrong scheme", func(t *testing.T) {
|
|
u, _ := url.Parse("ftp://example.com/file.txt")
|
|
err := protocol.ValidateURL(u, "http")
|
|
if err == nil {
|
|
t.Fatal("ValidateURL() for wrong scheme: error = nil, want error")
|
|
}
|
|
gogetErr, ok := err.(*core.GogetError)
|
|
if !ok {
|
|
t.Fatalf("error type = %T, want *core.GogetError", err)
|
|
}
|
|
expectedMsg := "expected scheme http, got ftp"
|
|
if gogetErr.Message != expectedMsg {
|
|
t.Errorf("Message = %q, want %q", gogetErr.Message, expectedMsg)
|
|
}
|
|
if gogetErr.URL != "ftp://example.com/file.txt" {
|
|
t.Errorf("URL = %q, want %q", gogetErr.URL, "ftp://example.com/file.txt")
|
|
}
|
|
})
|
|
|
|
t.Run("missing host", func(t *testing.T) {
|
|
u, _ := url.Parse("http:///file.txt")
|
|
err := protocol.ValidateURL(u, "http")
|
|
if err == nil {
|
|
t.Fatal("ValidateURL() for missing host: error = nil, want error")
|
|
}
|
|
gogetErr, ok := err.(*core.GogetError)
|
|
if !ok {
|
|
t.Fatalf("error type = %T, want *core.GogetError", err)
|
|
}
|
|
if gogetErr.Message != "missing host in URL" {
|
|
t.Errorf("Message = %q, want %q", gogetErr.Message, "missing host in URL")
|
|
}
|
|
})
|
|
|
|
t.Run("URL scheme is lowercased by Go parser", func(t *testing.T) {
|
|
u, _ := url.Parse("HTTP://example.com/file.txt")
|
|
err := protocol.ValidateURL(u, "http")
|
|
if err != nil {
|
|
t.Errorf("ValidateURL() error = %v, want nil (Go lowercases the scheme)", err)
|
|
}
|
|
})
|
|
|
|
t.Run("URL without path", func(t *testing.T) {
|
|
u, _ := url.Parse("http://example.com")
|
|
err := protocol.ValidateURL(u, "http")
|
|
if err != nil {
|
|
t.Errorf("ValidateURL() error = %v, want nil", err)
|
|
}
|
|
})
|
|
|
|
t.Run("URL with port", func(t *testing.T) {
|
|
u, _ := url.Parse("http://example.com:8080/file.txt")
|
|
err := protocol.ValidateURL(u, "http")
|
|
if err != nil {
|
|
t.Errorf("ValidateURL() error = %v, want nil", err)
|
|
}
|
|
})
|
|
|
|
t.Run("URL with query string", func(t *testing.T) {
|
|
u, _ := url.Parse("http://example.com/file.txt?token=abc")
|
|
err := protocol.ValidateURL(u, "http")
|
|
if err != nil {
|
|
t.Errorf("ValidateURL() error = %v, want nil", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- GetDefaultPort Tests ----------
|
|
|
|
func TestGetDefaultPort(t *testing.T) {
|
|
tests := []struct {
|
|
scheme string
|
|
expected int
|
|
}{
|
|
{"http", 80},
|
|
{"https", 443},
|
|
{"webdav", 80},
|
|
{"webdavs", 443},
|
|
{"ftp", 21},
|
|
{"ftps", 990},
|
|
{"sftp", 22},
|
|
{"unknown", 0},
|
|
{"gopher", 0},
|
|
{"", 0},
|
|
{"HTTP", 0},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.scheme, func(t *testing.T) {
|
|
got := protocol.GetDefaultPort(tt.scheme)
|
|
if got != tt.expected {
|
|
t.Errorf("GetDefaultPort(%q) = %d, want %d", tt.scheme, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ---------- Contains Tests ----------
|
|
|
|
func TestContains(t *testing.T) {
|
|
t.Run("feature present via SupportsResume", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume", "compression"},
|
|
})
|
|
if !bp.SupportsResume() {
|
|
t.Error("SupportsResume() = false, want true")
|
|
}
|
|
if !bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = false, want true")
|
|
}
|
|
if bp.SupportsUpload() {
|
|
t.Error("SupportsUpload() = true, want false")
|
|
}
|
|
if bp.SupportsRange() {
|
|
t.Error("SupportsRange() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("feature not present", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"upload", "range"},
|
|
})
|
|
if bp.SupportsResume() {
|
|
t.Error("SupportsResume() = true, want false")
|
|
}
|
|
if bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = true, want false")
|
|
}
|
|
if !bp.SupportsUpload() {
|
|
t.Error("SupportsUpload() = false, want true")
|
|
}
|
|
if !bp.SupportsRange() {
|
|
t.Error("SupportsRange() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("empty features", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{})
|
|
if bp.SupportsResume() {
|
|
t.Error("SupportsResume() = true, want false")
|
|
}
|
|
if bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = true, want false")
|
|
}
|
|
if bp.SupportsUpload() {
|
|
t.Error("SupportsUpload() = true, want false")
|
|
}
|
|
if bp.SupportsRange() {
|
|
t.Error("SupportsRange() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("single feature", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume"},
|
|
})
|
|
if !bp.SupportsResume() {
|
|
t.Error("SupportsResume() = false, want true")
|
|
}
|
|
if bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("all features", func(t *testing.T) {
|
|
bp := protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Features: []string{"resume", "compression", "upload", "range"},
|
|
})
|
|
if !bp.SupportsResume() {
|
|
t.Error("SupportsResume() = false, want true")
|
|
}
|
|
if !bp.SupportsCompression() {
|
|
t.Error("SupportsCompression() = false, want true")
|
|
}
|
|
if !bp.SupportsUpload() {
|
|
t.Error("SupportsUpload() = false, want true")
|
|
}
|
|
if !bp.SupportsRange() {
|
|
t.Error("SupportsRange() = false, want true")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- Registry Tests ----------
|
|
|
|
func TestNewRegistry(t *testing.T) {
|
|
t.Run("creates empty registry", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
if r == nil {
|
|
t.Fatal("NewRegistry() returned nil")
|
|
}
|
|
schemes := r.List()
|
|
if len(schemes) != 0 {
|
|
t.Errorf("List() returned %v, want empty slice", schemes)
|
|
}
|
|
})
|
|
|
|
t.Run("creates independent instances", func(t *testing.T) {
|
|
r1 := protocol.NewRegistry()
|
|
r2 := protocol.NewRegistry()
|
|
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
|
|
if err := r1.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
|
|
schemes := r2.List()
|
|
if len(schemes) != 0 {
|
|
t.Errorf("New registry should be independent, got %v", schemes)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRegister(t *testing.T) {
|
|
t.Run("registers http protocol", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
|
|
err := r.Register(httpProto)
|
|
if err != nil {
|
|
t.Errorf("Register() error = %v, want nil", err)
|
|
}
|
|
|
|
schemes := r.List()
|
|
if len(schemes) != 1 || schemes[0] != "http" {
|
|
t.Errorf("List() = %v, want [http]", schemes)
|
|
}
|
|
})
|
|
|
|
t.Run("registers multiple protocols", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
}),
|
|
}
|
|
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register(http) error = %v", err)
|
|
}
|
|
if err := r.Register(ftpProto); err != nil {
|
|
t.Fatalf("Register(ftp) error = %v", err)
|
|
}
|
|
|
|
schemes := r.List()
|
|
if len(schemes) != 2 {
|
|
t.Errorf("List() = %v, want [ftp http]", schemes)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRegisterDuplicate(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto1 := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP/1.1",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
httpProto2 := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP/2",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
|
|
if err := r.Register(httpProto1); err != nil {
|
|
t.Fatalf("First Register() error = %v", err)
|
|
}
|
|
|
|
err := r.Register(httpProto2)
|
|
if err == nil {
|
|
t.Fatal("Duplicate Register() error = nil, want error")
|
|
}
|
|
expected := "protocol already registered: http"
|
|
if err.Error() != expected {
|
|
t.Errorf("Error() = %q, want %q", err.Error(), expected)
|
|
}
|
|
|
|
u, _ := url.Parse("http://example.com/file.txt")
|
|
p, err := r.Get(u)
|
|
if err != nil {
|
|
t.Fatalf("Get() error = %v", err)
|
|
}
|
|
if p.Scheme() != "http" {
|
|
t.Errorf("Protocol scheme = %q, want %q", p.Scheme(), "http")
|
|
}
|
|
}
|
|
|
|
func TestRegisterEmptyScheme(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
emptyProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "Empty",
|
|
}),
|
|
}
|
|
|
|
err := r.Register(emptyProto)
|
|
if err == nil {
|
|
t.Fatal("Register() with empty scheme: error = nil, want error")
|
|
}
|
|
if err.Error() != "protocol scheme cannot be empty" {
|
|
t.Errorf("Error() = %q, want %q", err.Error(), "protocol scheme cannot be empty")
|
|
}
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
|
|
t.Run("gets http protocol", func(t *testing.T) {
|
|
u, _ := url.Parse("http://example.com/file.txt")
|
|
p, err := r.Get(u)
|
|
if err != nil {
|
|
t.Errorf("Get() error = %v, want nil", err)
|
|
}
|
|
if p == nil {
|
|
t.Fatal("Get() returned nil")
|
|
}
|
|
if p.Scheme() != "http" {
|
|
t.Errorf("Protocol scheme = %q, want %q", p.Scheme(), "http")
|
|
}
|
|
})
|
|
|
|
t.Run("gets https via normalization", func(t *testing.T) {
|
|
u, _ := url.Parse("https://example.com/file.txt")
|
|
p, err := r.Get(u)
|
|
if err != nil {
|
|
t.Errorf("Get(https) error = %v, want nil", err)
|
|
}
|
|
if p == nil {
|
|
t.Fatal("Get() returned nil")
|
|
}
|
|
if p.Scheme() != "http" {
|
|
t.Errorf("Protocol scheme = %q, want %q", p.Scheme(), "http")
|
|
}
|
|
})
|
|
|
|
t.Run("CanHandle returns true", func(t *testing.T) {
|
|
u, _ := url.Parse("http://example.com/file.txt")
|
|
p, err := r.Get(u)
|
|
if err != nil {
|
|
t.Errorf("Get() error = %v, want nil", err)
|
|
}
|
|
if !p.CanHandle(u) {
|
|
t.Error("CanHandle() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("CanHandle called after normalization", func(t *testing.T) {
|
|
r2 := protocol.NewRegistry()
|
|
canHandleCalled := false
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
}),
|
|
canHandleFn: func(u *url.URL) bool {
|
|
canHandleCalled = true
|
|
return u.Host != ""
|
|
},
|
|
}
|
|
if err := r2.Register(ftpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
|
|
u, _ := url.Parse("ftp://example.com/file.txt")
|
|
p, err := r2.Get(u)
|
|
if err != nil {
|
|
t.Errorf("Get() error = %v, want nil", err)
|
|
}
|
|
if !canHandleCalled {
|
|
t.Error("CanHandle() was not called")
|
|
}
|
|
if p == nil {
|
|
t.Fatal("Get() returned nil")
|
|
}
|
|
})
|
|
|
|
t.Run("CanHandle returns false", func(t *testing.T) {
|
|
r2 := protocol.NewRegistry()
|
|
restrictiveProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "Restrictive",
|
|
Scheme: "restrict",
|
|
}),
|
|
canHandleFn: func(u *url.URL) bool {
|
|
return false
|
|
},
|
|
}
|
|
if err := r2.Register(restrictiveProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
|
|
u, _ := url.Parse("restrict://example.com/file.txt")
|
|
_, err := r2.Get(u)
|
|
if err == nil {
|
|
t.Fatal("Get() with CanHandle=false: error = nil, want error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetUnsupported(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
|
|
t.Run("unsupported scheme returns error", func(t *testing.T) {
|
|
u, _ := url.Parse("gopher://example.com/file.txt")
|
|
_, err := r.Get(u)
|
|
if err == nil {
|
|
t.Fatal("Get(gopher) error = nil, want error")
|
|
}
|
|
expected := "unsupported protocol: gopher"
|
|
if err.Error() != expected {
|
|
t.Errorf("Error() = %q, want %q", err.Error(), expected)
|
|
}
|
|
})
|
|
|
|
t.Run("ftps maps to ftp", func(t *testing.T) {
|
|
u, _ := url.Parse("ftps://example.com/file.txt")
|
|
_, err := r.Get(u)
|
|
if err == nil {
|
|
t.Fatal("Get(ftps) with only http registered: error = nil, want error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetNilURL(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
|
|
_, err := r.Get(nil)
|
|
if err == nil {
|
|
t.Fatal("Get(nil) error = nil, want error")
|
|
}
|
|
if err.Error() != "url cannot be nil" {
|
|
t.Errorf("Error() = %q, want %q", err.Error(), "url cannot be nil")
|
|
}
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
t.Run("empty registry", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
schemes := r.List()
|
|
if schemes == nil {
|
|
t.Error("List() returned nil, want empty slice")
|
|
}
|
|
if len(schemes) != 0 {
|
|
t.Errorf("List() = %v, want empty", schemes)
|
|
}
|
|
})
|
|
|
|
t.Run("single protocol", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
r.Register(httpProto)
|
|
schemes := r.List()
|
|
if len(schemes) != 1 || schemes[0] != "http" {
|
|
t.Errorf("List() = %v, want [http]", schemes)
|
|
}
|
|
})
|
|
|
|
t.Run("multiple protocols sorted", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
}),
|
|
}
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
sftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "SFTP",
|
|
Scheme: "sftp",
|
|
}),
|
|
}
|
|
r.Register(httpProto)
|
|
r.Register(sftpProto)
|
|
r.Register(ftpProto)
|
|
|
|
schemes := r.List()
|
|
expected := []string{"ftp", "http", "sftp"}
|
|
if len(schemes) != len(expected) {
|
|
t.Errorf("List() = %v, want %v", schemes, expected)
|
|
return
|
|
}
|
|
for i, s := range schemes {
|
|
if s != expected[i] {
|
|
t.Errorf("List()[%d] = %q, want %q", i, s, expected[i])
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("not affected by external modifications", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
r.Register(httpProto)
|
|
|
|
schemes1 := r.List()
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
}),
|
|
}
|
|
r.Register(ftpProto)
|
|
|
|
if len(schemes1) != 1 {
|
|
t.Errorf("Previous List() result changed: got %d elements, want %d", len(schemes1), 1)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSupports(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
scheme string
|
|
want bool
|
|
}{
|
|
{"http", true},
|
|
{"https", true},
|
|
{"ftp", false},
|
|
{"ftps", false},
|
|
{"sftp", false},
|
|
{"unknown", false},
|
|
{"", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.scheme, func(t *testing.T) {
|
|
got := r.Supports(tt.scheme)
|
|
if got != tt.want {
|
|
t.Errorf("Supports(%q) = %v, want %v", tt.scheme, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("supports webdav via normalization", func(t *testing.T) {
|
|
r2 := protocol.NewRegistry()
|
|
webdavProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "WebDAV",
|
|
Scheme: "webdav",
|
|
}),
|
|
}
|
|
r2.Register(webdavProto)
|
|
|
|
if !r2.Supports("webdav") {
|
|
t.Error("Supports(webdav) = false, want true")
|
|
}
|
|
if !r2.Supports("webdavs") {
|
|
t.Error("Supports(webdavs) = false, want true")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCapabilities(t *testing.T) {
|
|
t.Run("returns empty map for empty registry", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
caps := r.Capabilities()
|
|
if caps == nil {
|
|
t.Error("Capabilities() returned nil, want empty map")
|
|
}
|
|
if len(caps) != 0 {
|
|
t.Errorf("Capabilities() = %v, want empty map", caps)
|
|
}
|
|
})
|
|
|
|
t.Run("returns capabilities for single protocol", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
Features: []string{"resume", "compression", "range"},
|
|
}),
|
|
}
|
|
r.Register(httpProto)
|
|
|
|
caps := r.Capabilities()
|
|
if len(caps) != 1 {
|
|
t.Errorf("len(Capabilities()) = %d, want %d", len(caps), 1)
|
|
}
|
|
features, ok := caps["http"]
|
|
if !ok {
|
|
t.Fatal("Capabilities() missing http key")
|
|
}
|
|
expected := []string{"resume", "compression", "range"}
|
|
if len(features) != len(expected) {
|
|
t.Errorf("features = %v, want %v", features, expected)
|
|
}
|
|
for i, f := range features {
|
|
if f != expected[i] {
|
|
t.Errorf("features[%d] = %q, want %q", i, f, expected[i])
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("returns capabilities for multiple protocols", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
Features: []string{"resume", "range"},
|
|
}),
|
|
}
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
Features: []string{"resume", "upload"},
|
|
}),
|
|
}
|
|
sftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "SFTP",
|
|
Scheme: "sftp",
|
|
}),
|
|
}
|
|
r.Register(httpProto)
|
|
r.Register(ftpProto)
|
|
r.Register(sftpProto)
|
|
|
|
caps := r.Capabilities()
|
|
if len(caps) != 3 {
|
|
t.Errorf("len(Capabilities()) = %d, want %d", len(caps), 3)
|
|
}
|
|
|
|
if _, ok := caps["http"]; !ok {
|
|
t.Error("Capabilities() missing http key")
|
|
}
|
|
if _, ok := caps["ftp"]; !ok {
|
|
t.Error("Capabilities() missing ftp key")
|
|
}
|
|
if _, ok := caps["sftp"]; !ok {
|
|
t.Error("Capabilities() missing sftp key")
|
|
}
|
|
|
|
if len(caps["sftp"]) != 0 {
|
|
t.Errorf("sftp capabilities = %v, want empty", caps["sftp"])
|
|
}
|
|
})
|
|
|
|
t.Run("map is independent copy", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
Features: []string{"resume"},
|
|
}),
|
|
}
|
|
r.Register(httpProto)
|
|
|
|
caps := r.Capabilities()
|
|
caps["http"] = []string{"modified"}
|
|
|
|
caps2 := r.Capabilities()
|
|
if len(caps2["http"]) != 1 || caps2["http"][0] != "resume" {
|
|
t.Errorf("Capabilities() returned aliased map: got %v, want [resume]", caps2["http"])
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- NormalizeScheme Tests ----------
|
|
|
|
func TestNormalizeScheme(t *testing.T) {
|
|
t.Run("https normalizes to http via Supports", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
if !r.Supports("https") {
|
|
t.Error("Supports(https) = false, want true (normalized to http)")
|
|
}
|
|
})
|
|
|
|
t.Run("https URL resolves via Get", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
u, _ := url.Parse("https://example.com/file.txt")
|
|
p, err := r.Get(u)
|
|
if err != nil {
|
|
t.Errorf("Get(https) error = %v, want nil", err)
|
|
}
|
|
if p.Scheme() != "http" {
|
|
t.Errorf("scheme = %q, want %q", p.Scheme(), "http")
|
|
}
|
|
})
|
|
|
|
t.Run("webdavs normalizes to webdav", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
webdavProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "WebDAV",
|
|
Scheme: "webdav",
|
|
}),
|
|
}
|
|
if err := r.Register(webdavProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
if !r.Supports("webdav") {
|
|
t.Error("Supports(webdav) = false, want true")
|
|
}
|
|
if !r.Supports("webdavs") {
|
|
t.Error("Supports(webdavs) = false, want true (normalized to webdav)")
|
|
}
|
|
})
|
|
|
|
t.Run("ftps normalizes to ftp", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
}),
|
|
}
|
|
if err := r.Register(ftpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
if !r.Supports("ftp") {
|
|
t.Error("Supports(ftp) = false, want true")
|
|
}
|
|
if !r.Supports("ftps") {
|
|
t.Error("Supports(ftps) = false, want true (normalized to ftp)")
|
|
}
|
|
})
|
|
|
|
t.Run("ftps URL resolves via Get", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
}),
|
|
}
|
|
if err := r.Register(ftpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
u, _ := url.Parse("ftps://example.com/file.txt")
|
|
p, err := r.Get(u)
|
|
if err != nil {
|
|
t.Errorf("Get(ftps) error = %v, want nil", err)
|
|
}
|
|
if p.Scheme() != "ftp" {
|
|
t.Errorf("scheme = %q, want %q", p.Scheme(), "ftp")
|
|
}
|
|
})
|
|
|
|
t.Run("http stays as http", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
if !r.Supports("http") {
|
|
t.Error("Supports(http) = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("unknown scheme not normalized", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
if r.Supports("") {
|
|
t.Error("Supports(\"\") = true, want false")
|
|
}
|
|
if r.Supports("unknown") {
|
|
t.Error("Supports(unknown) = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("case insensitive normalization", func(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register() error = %v", err)
|
|
}
|
|
if !r.Supports("HTTPS") {
|
|
t.Error("Supports(HTTPS) = false, want true (case-insensitive normalization)")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------- Global Registry Tests ----------
|
|
|
|
func TestGlobalRegistry(t *testing.T) {
|
|
r := protocol.GlobalRegistry
|
|
if r == nil {
|
|
t.Fatal("GlobalRegistry is nil")
|
|
}
|
|
|
|
schemes := r.List()
|
|
if schemes == nil {
|
|
t.Error("List() on global registry returned nil")
|
|
}
|
|
}
|
|
|
|
func TestGlobalRegister(t *testing.T) {
|
|
uniqueProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "TestProto",
|
|
Scheme: "testglobal",
|
|
}),
|
|
}
|
|
|
|
err := protocol.Register(uniqueProto)
|
|
if err != nil {
|
|
t.Errorf("Register() on global registry: error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGlobalGet(t *testing.T) {
|
|
_, err := protocol.Get(nil)
|
|
if err == nil {
|
|
t.Fatal("Get(nil) on global registry: error = nil, want error")
|
|
}
|
|
if err.Error() != "url cannot be nil" {
|
|
t.Errorf("Error() = %q, want %q", err.Error(), "url cannot be nil")
|
|
}
|
|
}
|
|
|
|
// ---------- Concurrency Tests ----------
|
|
|
|
func TestRegistryConcurrency(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
Features: []string{"resume", "compression", "range"},
|
|
}),
|
|
}
|
|
if err := r.Register(httpProto); err != nil {
|
|
t.Fatalf("Register(http) error = %v", err)
|
|
}
|
|
|
|
done := make(chan bool, 10)
|
|
for i := 0; i < 10; i++ {
|
|
go func() {
|
|
_ = r.List()
|
|
_ = r.Supports("http")
|
|
_ = r.Supports("https")
|
|
_ = r.Supports("ftp")
|
|
_ = r.Capabilities()
|
|
u, _ := url.Parse("http://example.com/file.txt")
|
|
_, _ = r.Get(u)
|
|
done <- true
|
|
}()
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func TestRegistryRegisterReadConcurrency(t *testing.T) {
|
|
r := protocol.NewRegistry()
|
|
|
|
httpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "HTTP",
|
|
Scheme: "http",
|
|
}),
|
|
}
|
|
r.Register(httpProto)
|
|
|
|
done := make(chan bool, 2)
|
|
go func() {
|
|
ftpProto := &mockProtocol{
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
|
Name: "FTP",
|
|
Scheme: "ftp",
|
|
}),
|
|
}
|
|
r.Register(ftpProto)
|
|
done <- true
|
|
}()
|
|
go func() {
|
|
_ = r.List()
|
|
_ = r.Supports("http")
|
|
_ = r.Capabilities()
|
|
u, _ := url.Parse("http://example.com/file.txt")
|
|
_, _ = r.Get(u)
|
|
done <- true
|
|
}()
|
|
|
|
<-done
|
|
<-done
|
|
}
|