package service import ( "encoding/json" "log/slog" "os" "sync" "testing" "github.com/shankar0123/certctl/internal/crypto" "github.com/shankar0123/certctl/internal/domain" ) func registryTestLogger() *slog.Logger { return slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) } func TestIssuerRegistry_GetSet(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) mock := &mockIssuerConnector{} reg.Set("iss-test", mock) conn, ok := reg.Get("iss-test") if !ok { t.Fatal("expected to find iss-test in registry") } if conn == nil { t.Fatal("expected non-nil connector") } } func TestIssuerRegistry_GetNotFound(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) _, ok := reg.Get("nonexistent") if ok { t.Fatal("expected not to find nonexistent issuer") } } func TestIssuerRegistry_Remove(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) reg.Set("iss-test", &mockIssuerConnector{}) reg.Remove("iss-test") _, ok := reg.Get("iss-test") if ok { t.Fatal("expected issuer to be removed") } } func TestIssuerRegistry_List(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) reg.Set("iss-a", &mockIssuerConnector{}) reg.Set("iss-b", &mockIssuerConnector{}) list := reg.List() if len(list) != 2 { t.Fatalf("expected 2 issuers, got %d", len(list)) } // Verify List returns a copy (modifying it doesn't affect registry) delete(list, "iss-a") if reg.Len() != 2 { t.Fatal("deleting from List() copy should not affect registry") } } func TestIssuerRegistry_Len(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) if reg.Len() != 0 { t.Fatalf("expected empty registry, got %d", reg.Len()) } reg.Set("iss-a", &mockIssuerConnector{}) if reg.Len() != 1 { t.Fatalf("expected 1 issuer, got %d", reg.Len()) } } func TestIssuerRegistry_Rebuild_Enabled(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) configs := []*domain.Issuer{ { ID: "iss-local", Name: "Local CA", Type: "local", Config: json.RawMessage(`{}`), Enabled: true, }, { ID: "iss-disabled", Name: "Disabled", Type: "local", Config: json.RawMessage(`{}`), Enabled: false, }, } err := reg.Rebuild(configs, nil) if err != nil { t.Fatalf("Rebuild failed: %v", err) } if reg.Len() != 1 { t.Fatalf("expected 1 enabled issuer, got %d", reg.Len()) } _, ok := reg.Get("iss-local") if !ok { t.Fatal("expected iss-local in registry") } _, ok = reg.Get("iss-disabled") if ok { t.Fatal("disabled issuer should not be in registry") } } func TestIssuerRegistry_Rebuild_WithEncryption(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) key := crypto.DeriveKey("test-key") configJSON := []byte(`{"ca_common_name":"Encrypted CA"}`) encrypted, err := crypto.Encrypt(configJSON, key) if err != nil { t.Fatalf("encrypt failed: %v", err) } configs := []*domain.Issuer{ { ID: "iss-encrypted", Name: "Encrypted Local CA", Type: "local", EncryptedConfig: encrypted, Enabled: true, }, } err = reg.Rebuild(configs, key) if err != nil { t.Fatalf("Rebuild with encryption failed: %v", err) } _, ok := reg.Get("iss-encrypted") if !ok { t.Fatal("expected iss-encrypted in registry") } } func TestIssuerRegistry_Rebuild_NilKeyFallback(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) configs := []*domain.Issuer{ { ID: "iss-plain", Name: "Plain Config", Type: "local", Config: json.RawMessage(`{}`), Enabled: true, }, } // nil key should work — falls back to config column err := reg.Rebuild(configs, nil) if err != nil { t.Fatalf("Rebuild with nil key failed: %v", err) } _, ok := reg.Get("iss-plain") if !ok { t.Fatal("expected iss-plain in registry") } } func TestIssuerRegistry_Rebuild_InvalidConfig(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) configs := []*domain.Issuer{ { ID: "iss-bad", Name: "Bad Config", Type: "UnknownType", Config: json.RawMessage(`{}`), Enabled: true, }, { ID: "iss-good", Name: "Good Config", Type: "local", Config: json.RawMessage(`{}`), Enabled: true, }, } // Should return an error indicating partial failure, but still load valid issuers err := reg.Rebuild(configs, nil) if err == nil { t.Fatal("Rebuild should return error when some issuers fail to load") } // Despite the error, valid issuers should be loaded if reg.Len() != 1 { t.Fatalf("expected 1 valid issuer, got %d", reg.Len()) } _, ok := reg.Get("iss-good") if !ok { t.Fatal("expected iss-good in registry") } } func TestIssuerRegistry_Rebuild_ReplacesExisting(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) // Set up initial state reg.Set("iss-old", &mockIssuerConnector{}) configs := []*domain.Issuer{ { ID: "iss-new", Name: "New Issuer", Type: "local", Config: json.RawMessage(`{}`), Enabled: true, }, } err := reg.Rebuild(configs, nil) if err != nil { t.Fatalf("Rebuild failed: %v", err) } _, ok := reg.Get("iss-old") if ok { t.Fatal("old issuer should have been replaced") } _, ok = reg.Get("iss-new") if !ok { t.Fatal("new issuer should be present") } } func TestIssuerRegistry_ConcurrentAccess(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(3) id := "iss-concurrent" go func() { defer wg.Done() reg.Set(id, &mockIssuerConnector{}) }() go func() { defer wg.Done() reg.Get(id) }() go func() { defer wg.Done() reg.List() }() } wg.Wait() // No race detector panics = success } func TestIssuerRegistry_Rebuild_Empty(t *testing.T) { reg := NewIssuerRegistry(registryTestLogger()) reg.Set("iss-existing", &mockIssuerConnector{}) err := reg.Rebuild([]*domain.Issuer{}, nil) if err != nil { t.Fatalf("Rebuild with empty configs failed: %v", err) } if reg.Len() != 0 { t.Fatalf("expected empty registry after rebuild with no configs, got %d", reg.Len()) } }