package dnsprovider import ( "sync" "testing" "time" ) // mockProvider is a test implementation of ProviderPlugin type mockProvider struct { providerType string } func (m *mockProvider) Type() string { return m.providerType } func (m *mockProvider) Metadata() ProviderMetadata { return ProviderMetadata{ Type: m.providerType, Name: "Mock Provider", Version: "1.0.0", IsBuiltIn: false, } } func (m *mockProvider) Init() error { return nil } func (m *mockProvider) Cleanup() error { return nil } func (m *mockProvider) ValidateCredentials(creds map[string]string) error { return nil } func (m *mockProvider) TestCredentials(creds map[string]string) error { return nil } func (m *mockProvider) RequiredCredentialFields() []CredentialFieldSpec { return []CredentialFieldSpec{} } func (m *mockProvider) OptionalCredentialFields() []CredentialFieldSpec { return []CredentialFieldSpec{} } func (m *mockProvider) SupportsMultiCredential() bool { return false } func (m *mockProvider) BuildCaddyConfig(creds map[string]string) map[string]any { return map[string]any{} } func (m *mockProvider) BuildCaddyConfigForZone(baseDomain string, creds map[string]string) map[string]any { return map[string]any{} } func (m *mockProvider) PropagationTimeout() time.Duration { return time.Minute } func (m *mockProvider) PollingInterval() time.Duration { return 2 * time.Second } // TestNewRegistry tests creating a new registry instance func TestNewRegistry(t *testing.T) { r := NewRegistry() if r == nil { t.Fatal("NewRegistry returned nil") } if r.Count() != 0 { t.Errorf("expected empty registry, got %d providers", r.Count()) } } // TestGlobal tests the global registry singleton func TestGlobal(t *testing.T) { r1 := Global() r2 := Global() if r1 != r2 { t.Error("Global() should return the same instance") } if r1 == nil { t.Fatal("Global() returned nil") } } // TestRegister tests registering providers func TestRegister(t *testing.T) { tests := []struct { name string provider ProviderPlugin wantErr error }{ { name: "successful registration", provider: &mockProvider{providerType: "test-provider"}, wantErr: nil, }, { name: "nil provider", provider: nil, wantErr: ErrInvalidPlugin, }, { name: "empty provider type", provider: &mockProvider{providerType: ""}, wantErr: ErrInvalidPlugin, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := NewRegistry() err := r.Register(tt.provider) if err != tt.wantErr { t.Errorf("Register() error = %v, wantErr %v", err, tt.wantErr) } // If successful, verify provider was registered if err == nil && tt.provider != nil { if !r.IsSupported(tt.provider.Type()) { t.Errorf("provider %s was not registered", tt.provider.Type()) } } }) } } // TestRegister_Duplicate tests duplicate registration func TestRegister_Duplicate(t *testing.T) { r := NewRegistry() provider := &mockProvider{providerType: "duplicate-test"} // First registration should succeed err := r.Register(provider) if err != nil { t.Fatalf("first registration failed: %v", err) } // Second registration should fail err = r.Register(provider) if err != ErrProviderAlreadyRegistered { t.Errorf("expected ErrProviderAlreadyRegistered, got %v", err) } // Count should still be 1 if r.Count() != 1 { t.Errorf("expected count 1, got %d", r.Count()) } } // TestGet tests retrieving providers func TestGet(t *testing.T) { r := NewRegistry() provider := &mockProvider{providerType: "test-get"} if err := r.Register(provider); err != nil { t.Fatalf("failed to register provider: %v", err) } tests := []struct { name string providerType string wantOK bool }{ { name: "existing provider", providerType: "test-get", wantOK: true, }, { name: "non-existent provider", providerType: "does-not-exist", wantOK: false, }, { name: "empty provider type", providerType: "", wantOK: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotProvider, gotOK := r.Get(tt.providerType) if gotOK != tt.wantOK { t.Errorf("Get() ok = %v, want %v", gotOK, tt.wantOK) } if tt.wantOK { if gotProvider == nil { t.Error("expected non-nil provider for existing type") } if gotProvider.Type() != tt.providerType { t.Errorf("got provider type %s, want %s", gotProvider.Type(), tt.providerType) } } else { if gotProvider != nil { t.Error("expected nil provider for non-existent type") } } }) } } // TestList tests listing all providers func TestList(t *testing.T) { r := NewRegistry() // Test empty registry list := r.List() if len(list) != 0 { t.Errorf("expected empty list, got %d providers", len(list)) } // Register multiple providers providers := []*mockProvider{ {providerType: "provider-c"}, {providerType: "provider-a"}, {providerType: "provider-b"}, } for _, p := range providers { if err := r.Register(p); err != nil { t.Fatalf("failed to register provider %s: %v", p.Type(), err) } } // Get list (should be sorted) list = r.List() if len(list) != 3 { t.Fatalf("expected 3 providers, got %d", len(list)) } // Verify alphabetical ordering expectedOrder := []string{"provider-a", "provider-b", "provider-c"} for i, p := range list { if p.Type() != expectedOrder[i] { t.Errorf("list[%d] = %s, want %s", i, p.Type(), expectedOrder[i]) } } } // TestTypes tests listing provider types func TestTypes(t *testing.T) { r := NewRegistry() // Test empty registry types := r.Types() if len(types) != 0 { t.Errorf("expected empty types, got %d", len(types)) } // Register multiple providers providers := []*mockProvider{ {providerType: "zebra"}, {providerType: "alpha"}, {providerType: "beta"}, } for _, p := range providers { if err := r.Register(p); err != nil { t.Fatalf("failed to register provider %s: %v", p.Type(), err) } } // Get types (should be sorted) types = r.Types() if len(types) != 3 { t.Fatalf("expected 3 types, got %d", len(types)) } // Verify alphabetical ordering expectedOrder := []string{"alpha", "beta", "zebra"} for i, typ := range types { if typ != expectedOrder[i] { t.Errorf("types[%d] = %s, want %s", i, typ, expectedOrder[i]) } } } // TestIsSupported tests checking provider support func TestIsSupported(t *testing.T) { r := NewRegistry() // Register a provider provider := &mockProvider{providerType: "supported-test"} if err := r.Register(provider); err != nil { t.Fatalf("failed to register provider: %v", err) } tests := []struct { name string providerType string want bool }{ { name: "supported provider", providerType: "supported-test", want: true, }, { name: "unsupported provider", providerType: "unsupported", want: false, }, { name: "empty string", providerType: "", want: false, }, { name: "case sensitivity", providerType: "SUPPORTED-TEST", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := r.IsSupported(tt.providerType) if got != tt.want { t.Errorf("IsSupported(%q) = %v, want %v", tt.providerType, got, tt.want) } }) } } // TestUnregister tests removing providers func TestUnregister(t *testing.T) { r := NewRegistry() // Register a provider provider := &mockProvider{providerType: "unregister-test"} if err := r.Register(provider); err != nil { t.Fatalf("failed to register provider: %v", err) } // Verify it's registered if !r.IsSupported("unregister-test") { t.Fatal("provider not registered") } // Unregister it r.Unregister("unregister-test") // Verify it's gone if r.IsSupported("unregister-test") { t.Error("provider still registered after unregister") } // Unregister non-existent (should not panic) r.Unregister("does-not-exist") // Count should be 0 if r.Count() != 0 { t.Errorf("expected count 0, got %d", r.Count()) } } // TestCount tests counting registered providers func TestCount(t *testing.T) { r := NewRegistry() // Initial count should be 0 if r.Count() != 0 { t.Errorf("expected count 0, got %d", r.Count()) } // Register providers for i := 1; i <= 5; i++ { provider := &mockProvider{providerType: "test-" + string(rune('a'+i-1))} if err := r.Register(provider); err != nil { t.Fatalf("failed to register provider: %v", err) } if r.Count() != i { t.Errorf("after registering %d providers, count = %d", i, r.Count()) } } // Unregister one r.Unregister("test-a") if r.Count() != 4 { t.Errorf("after unregistering, count = %d, want 4", r.Count()) } } // TestClear tests clearing all providers func TestClear(t *testing.T) { r := NewRegistry() // Register multiple providers for i := 0; i < 3; i++ { provider := &mockProvider{providerType: "clear-test-" + string(rune('a'+i))} if err := r.Register(provider); err != nil { t.Fatalf("failed to register provider: %v", err) } } // Verify count if r.Count() != 3 { t.Fatalf("expected count 3, got %d", r.Count()) } // Clear registry r.Clear() // Verify empty if r.Count() != 0 { t.Errorf("after clear, count = %d, want 0", r.Count()) } // Verify no providers are supported if r.IsSupported("clear-test-a") { t.Error("provider still supported after clear") } // List should be empty if len(r.List()) != 0 { t.Errorf("list not empty after clear, got %d providers", len(r.List())) } } // TestConcurrency tests thread-safe operations func TestConcurrency(t *testing.T) { r := NewRegistry() var wg sync.WaitGroup // Concurrent registrations for i := 0; i < 10; i++ { wg.Add(1) go func(n int) { defer wg.Done() provider := &mockProvider{providerType: "concurrent-" + string(rune('a'+n))} _ = r.Register(provider) }(i) } // Concurrent reads for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() _ = r.List() _ = r.Types() _ = r.IsSupported("concurrent-a") _, _ = r.Get("concurrent-a") }() } wg.Wait() // Verify final state if r.Count() != 10 { t.Errorf("expected 10 providers after concurrent registration, got %d", r.Count()) } } // TestRegistry_Operations tests combined operations func TestRegistry_Operations(t *testing.T) { r := NewRegistry() // Start with empty registry if r.Count() != 0 { t.Errorf("new registry should be empty") } // Register providers p1 := &mockProvider{providerType: "provider1"} p2 := &mockProvider{providerType: "provider2"} p3 := &mockProvider{providerType: "provider3"} if err := r.Register(p1); err != nil { t.Fatalf("failed to register p1: %v", err) } if err := r.Register(p2); err != nil { t.Fatalf("failed to register p2: %v", err) } if err := r.Register(p3); err != nil { t.Fatalf("failed to register p3: %v", err) } // Verify all are registered for _, p := range []ProviderPlugin{p1, p2, p3} { if !r.IsSupported(p.Type()) { t.Errorf("provider %s not registered", p.Type()) } retrieved, ok := r.Get(p.Type()) if !ok { t.Errorf("failed to get provider %s", p.Type()) } if retrieved.Type() != p.Type() { t.Errorf("retrieved wrong provider: got %s, want %s", retrieved.Type(), p.Type()) } } // List should contain all 3 list := r.List() if len(list) != 3 { t.Errorf("list length = %d, want 3", len(list)) } // Types should contain all 3 types := r.Types() if len(types) != 3 { t.Errorf("types length = %d, want 3", len(types)) } // Unregister one r.Unregister("provider2") // Verify count decreased if r.Count() != 2 { t.Errorf("after unregister, count = %d, want 2", r.Count()) } // Verify p2 is gone if r.IsSupported("provider2") { t.Error("provider2 still supported after unregister") } // Clear all r.Clear() // Verify empty if r.Count() != 0 { t.Errorf("after clear, count = %d, want 0", r.Count()) } if len(r.List()) != 0 { t.Error("list not empty after clear") } if len(r.Types()) != 0 { t.Error("types not empty after clear") } }