- Updated context key definitions in dns_provider_service_test.go to use string constants instead of custom types for user_id, client_ip, and user_agent. - Ensured proper context values are set in audit logging tests to avoid defaulting to "system" or empty values. - Enhanced in-memory SQLite database setup in credential_service_test.go to use WAL mode and busy timeout for better concurrency during tests.
1813 lines
54 KiB
Go
1813 lines
54 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/crypto"
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
_ "github.com/Wikid82/charon/backend/pkg/dnsprovider/builtin" // Auto-register DNS providers
|
|
)
|
|
|
|
// Context keys for test setup (using plain strings to match service expectations)
|
|
const (
|
|
testUserIDKey = "user_id"
|
|
testClientIPKey = "client_ip"
|
|
testUserAgentKey = "user_agent"
|
|
)
|
|
|
|
// setupTestDB creates an in-memory SQLite database for testing.
|
|
func setupDNSProviderTestDB(t *testing.T) (*gorm.DB, *crypto.EncryptionService) {
|
|
t.Helper()
|
|
|
|
// Use shared cache memory database with mutex for proper test isolation
|
|
// This prevents "no such table" errors that occur with :memory: databases
|
|
// when tests run in parallel or have timing issues
|
|
dbPath := ":memory:?cache=shared&mode=memory&_mutex=full"
|
|
|
|
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
// Disable prepared statements to avoid cache issues
|
|
PrepareStmt: false,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Get underlying SQL DB for connection pool configuration
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
|
|
// Force single connection to prevent parallel access issues
|
|
sqlDB.SetMaxOpenConns(1)
|
|
sqlDB.SetMaxIdleConns(1)
|
|
|
|
// Auto-migrate schema - SecurityAudit must be migrated FIRST before creating service
|
|
// because DNSProviderService starts a background goroutine that writes audit logs
|
|
err = db.AutoMigrate(&models.SecurityAudit{}, &models.DNSProvider{})
|
|
require.NoError(t, err)
|
|
|
|
// Verify tables were created
|
|
if !db.Migrator().HasTable(&models.DNSProvider{}) {
|
|
t.Fatal("failed to create dns_providers table")
|
|
}
|
|
if !db.Migrator().HasTable(&models.SecurityAudit{}) {
|
|
t.Fatal("failed to create security_audits table")
|
|
}
|
|
|
|
// Create encryption service with test key
|
|
encryptor, err := crypto.NewEncryptionService("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") // 32-byte key in base64
|
|
require.NoError(t, err)
|
|
|
|
// Register cleanup
|
|
t.Cleanup(func() {
|
|
_ = sqlDB.Close()
|
|
})
|
|
|
|
return db, encryptor
|
|
}
|
|
|
|
func TestDNSProviderService_Create(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
req CreateDNSProviderRequest
|
|
wantErr bool
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "valid cloudflare provider",
|
|
req: CreateDNSProviderRequest{
|
|
Name: "Test Cloudflare",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{
|
|
"api_token": "test-token-123",
|
|
},
|
|
PropagationTimeout: 120,
|
|
PollingInterval: 5,
|
|
IsDefault: true,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid route53 provider with defaults",
|
|
req: CreateDNSProviderRequest{
|
|
Name: "Test Route53",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{
|
|
"access_key_id": "AKIAIOSFODNN7EXAMPLE",
|
|
"secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"region": "us-east-1",
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid provider type",
|
|
req: CreateDNSProviderRequest{
|
|
Name: "Invalid Provider",
|
|
ProviderType: "invalid",
|
|
Credentials: map[string]string{
|
|
"api_key": "test",
|
|
},
|
|
},
|
|
wantErr: true,
|
|
expectedErr: ErrInvalidProviderType,
|
|
},
|
|
{
|
|
name: "missing required credentials",
|
|
req: CreateDNSProviderRequest{
|
|
Name: "Incomplete Cloudflare",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{},
|
|
},
|
|
wantErr: true,
|
|
expectedErr: ErrInvalidCredentials,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
provider, err := service.Create(ctx, tt.req)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.expectedErr != nil {
|
|
assert.ErrorIs(t, err, tt.expectedErr)
|
|
}
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, provider.ID)
|
|
assert.NotEmpty(t, provider.UUID)
|
|
assert.Equal(t, tt.req.Name, provider.Name)
|
|
assert.Equal(t, tt.req.ProviderType, provider.ProviderType)
|
|
assert.True(t, provider.Enabled)
|
|
assert.NotEmpty(t, provider.CredentialsEncrypted)
|
|
|
|
// Verify defaults were set
|
|
if tt.req.PropagationTimeout == 0 {
|
|
assert.Equal(t, 120, provider.PropagationTimeout)
|
|
}
|
|
if tt.req.PollingInterval == 0 {
|
|
assert.Equal(t, 5, provider.PollingInterval)
|
|
}
|
|
|
|
// Verify credentials are encrypted (not plaintext)
|
|
assert.NotContains(t, provider.CredentialsEncrypted, "api_token")
|
|
assert.NotContains(t, provider.CredentialsEncrypted, "test-token")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDNSProviderService_DefaultProviderLogic(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create first default provider
|
|
provider1, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "First Default",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{
|
|
"api_token": "token1",
|
|
},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, provider1.IsDefault)
|
|
|
|
// Create second default provider - should unset first
|
|
provider2, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Second Default",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
"secret_access_key": "secret",
|
|
"region": "us-east-1",
|
|
},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, provider2.IsDefault)
|
|
|
|
// Verify first provider is no longer default
|
|
updatedProvider1, err := service.Get(ctx, provider1.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, updatedProvider1.IsDefault)
|
|
}
|
|
|
|
func TestDNSProviderService_List(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create multiple providers
|
|
_, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Cloudflare",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Route53",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
"secret_access_key": "secret",
|
|
"region": "us-east-1",
|
|
},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// List all providers
|
|
providers, err := service.List(ctx)
|
|
require.NoError(t, err)
|
|
assert.Len(t, providers, 2)
|
|
|
|
// Verify default provider is first (ordered by is_default DESC)
|
|
assert.True(t, providers[0].IsDefault)
|
|
}
|
|
|
|
func TestDNSProviderService_Get(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
created, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Get the provider
|
|
provider, err := service.Get(ctx, created.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, created.ID, provider.ID)
|
|
assert.Equal(t, "Test Provider", provider.Name)
|
|
|
|
// Get non-existent provider
|
|
_, err = service.Get(ctx, 9999)
|
|
assert.ErrorIs(t, err, ErrDNSProviderNotFound)
|
|
}
|
|
|
|
func TestDNSProviderService_Update(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
created, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Original Name",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "original-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("update name only", func(t *testing.T) {
|
|
newName := "Updated Name"
|
|
updated, err := service.Update(ctx, created.ID, UpdateDNSProviderRequest{
|
|
Name: &newName,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Updated Name", updated.Name)
|
|
assert.True(t, updated.Enabled) // Should remain unchanged
|
|
})
|
|
|
|
t.Run("update credentials", func(t *testing.T) {
|
|
newCreds := map[string]string{"api_token": "new-token"}
|
|
updated, err := service.Update(ctx, created.ID, UpdateDNSProviderRequest{
|
|
Credentials: newCreds,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify credentials were updated by decrypting
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, updated.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "new-token", decrypted["api_token"])
|
|
})
|
|
|
|
t.Run("update enabled status", func(t *testing.T) {
|
|
enabled := false
|
|
updated, err := service.Update(ctx, created.ID, UpdateDNSProviderRequest{
|
|
Enabled: &enabled,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.False(t, updated.Enabled)
|
|
})
|
|
|
|
t.Run("update non-existent provider", func(t *testing.T) {
|
|
name := "Test"
|
|
_, err := service.Update(ctx, 9999, UpdateDNSProviderRequest{
|
|
Name: &name,
|
|
})
|
|
assert.ErrorIs(t, err, ErrDNSProviderNotFound)
|
|
})
|
|
|
|
t.Run("update to set default", func(t *testing.T) {
|
|
isDefault := true
|
|
updated, err := service.Update(ctx, created.ID, UpdateDNSProviderRequest{
|
|
IsDefault: &isDefault,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, updated.IsDefault)
|
|
})
|
|
}
|
|
|
|
func TestDNSProviderService_Delete(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
created, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "To Delete",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Delete the provider
|
|
err = service.Delete(ctx, created.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Verify it's deleted
|
|
_, err = service.Get(ctx, created.ID)
|
|
assert.ErrorIs(t, err, ErrDNSProviderNotFound)
|
|
|
|
// Delete non-existent provider
|
|
err = service.Delete(ctx, 9999)
|
|
assert.ErrorIs(t, err, ErrDNSProviderNotFound)
|
|
}
|
|
|
|
func TestDNSProviderService_GetDecryptedCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
testCreds := map[string]string{
|
|
"api_token": "secret-token-123",
|
|
"extra": "data",
|
|
}
|
|
created, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: testCreds,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Get decrypted credentials
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, created.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, testCreds, decrypted)
|
|
|
|
// Verify last_used_at was updated
|
|
provider, err := service.Get(ctx, created.ID)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, provider.LastUsedAt)
|
|
}
|
|
|
|
func TestDNSProviderService_TestCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
t.Run("valid credentials", func(t *testing.T) {
|
|
result, err := service.TestCredentials(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
assert.NotEmpty(t, result.Message)
|
|
})
|
|
|
|
t.Run("invalid provider type", func(t *testing.T) {
|
|
result, err := service.TestCredentials(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "invalid",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Success)
|
|
assert.Equal(t, "INVALID_PROVIDER_TYPE", result.Code)
|
|
})
|
|
|
|
t.Run("missing credentials", func(t *testing.T) {
|
|
result, err := service.TestCredentials(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{"access_key_id": "key"}, // Missing secret and region
|
|
})
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Success)
|
|
assert.Equal(t, "INVALID_CREDENTIALS", result.Code)
|
|
})
|
|
}
|
|
|
|
func TestDNSProviderService_Test(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
created, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Test the provider
|
|
result, err := service.Test(ctx, created.ID)
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
|
|
// Verify statistics were updated
|
|
provider, err := service.Get(ctx, created.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, provider.SuccessCount)
|
|
assert.Equal(t, 0, provider.FailureCount)
|
|
assert.NotNil(t, provider.LastUsedAt)
|
|
assert.Empty(t, provider.LastError)
|
|
}
|
|
|
|
func TestValidateCredentials(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
providerType string
|
|
credentials map[string]string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid cloudflare",
|
|
providerType: "cloudflare",
|
|
credentials: map[string]string{"api_token": "token"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid route53",
|
|
providerType: "route53",
|
|
credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
"secret_access_key": "secret",
|
|
"region": "us-east-1",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "missing field",
|
|
providerType: "route53",
|
|
credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
// Missing secret_access_key and region
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty field value",
|
|
providerType: "cloudflare",
|
|
credentials: map[string]string{"api_token": ""},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid provider type",
|
|
providerType: "invalid",
|
|
credentials: map[string]string{"api_token": "token"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validateCredentials(tt.providerType, tt.credentials)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsValidProviderType(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
providerType string
|
|
want bool
|
|
}{
|
|
{"cloudflare", "cloudflare", true},
|
|
{"route53", "route53", true},
|
|
{"digitalocean", "digitalocean", true},
|
|
{"invalid", "invalid", false},
|
|
{"empty", "", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.want, isValidProviderType(tt.providerType))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCredentialEncryptionRoundtrip(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
originalCreds := map[string]string{
|
|
"api_token": "super-secret-token",
|
|
"api_key": "another-secret",
|
|
"extra_data": "sensitive",
|
|
}
|
|
|
|
// Create provider with credentials
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Encryption Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: originalCreds,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify credentials are encrypted in database
|
|
var dbProvider models.DNSProvider
|
|
err = db.First(&dbProvider, provider.ID).Error
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, dbProvider.CredentialsEncrypted, "super-secret-token")
|
|
assert.NotContains(t, dbProvider.CredentialsEncrypted, "another-secret")
|
|
|
|
// Decrypt and verify
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, originalCreds, decrypted)
|
|
}
|
|
|
|
func TestUpdatePreservesCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
originalCreds := map[string]string{"api_token": "original-token"}
|
|
|
|
// Create provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: originalCreds,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Update without providing credentials
|
|
newName := "Updated Name"
|
|
updated, err := service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Name: &newName,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify credentials were preserved
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, updated.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, originalCreds, decrypted)
|
|
}
|
|
|
|
func TestEncryptionServiceIntegration(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
|
|
testData := map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
}
|
|
|
|
// Encrypt
|
|
jsonData, err := json.Marshal(testData)
|
|
require.NoError(t, err)
|
|
|
|
encrypted, err := encryptor.Encrypt(jsonData)
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, encrypted)
|
|
|
|
// Store in database
|
|
provider := &models.DNSProvider{
|
|
UUID: "test-uuid",
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
CredentialsEncrypted: encrypted,
|
|
}
|
|
err = db.Create(provider).Error
|
|
require.NoError(t, err)
|
|
|
|
// Retrieve and decrypt
|
|
var retrieved models.DNSProvider
|
|
err = db.First(&retrieved, provider.ID).Error
|
|
require.NoError(t, err)
|
|
|
|
decrypted, err := encryptor.Decrypt(retrieved.CredentialsEncrypted)
|
|
require.NoError(t, err)
|
|
|
|
var result map[string]string
|
|
err = json.Unmarshal(decrypted, &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, testData, result)
|
|
}
|
|
|
|
func TestDNSProviderService_TestFailure(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create provider with invalid credentials structure (will fail decryption in real scenario)
|
|
provider := &models.DNSProvider{
|
|
UUID: "test-uuid",
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
CredentialsEncrypted: "invalid-encrypted-data",
|
|
}
|
|
err := db.Create(provider).Error
|
|
require.NoError(t, err)
|
|
|
|
// Test should handle decryption failure gracefully
|
|
result, err := service.Test(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Success)
|
|
assert.Equal(t, "DECRYPTION_ERROR", result.Code)
|
|
}
|
|
|
|
func TestDNSProviderService_GetDecryptedCredentialsError(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create provider with invalid encrypted data
|
|
provider := &models.DNSProvider{
|
|
UUID: "test-uuid",
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
CredentialsEncrypted: "not-valid-base64",
|
|
}
|
|
err := db.Create(provider).Error
|
|
require.NoError(t, err)
|
|
|
|
// Should fail to decrypt
|
|
_, err = service.GetDecryptedCredentials(ctx, provider.ID)
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrDecryptionFailed)
|
|
}
|
|
|
|
func TestDNSProviderService_GetDecryptedCredentials_InvalidJSON_ReturnsErrDecryptionFailed(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
ciphertext, err := encryptor.Encrypt([]byte("not-json"))
|
|
require.NoError(t, err)
|
|
|
|
provider := &models.DNSProvider{
|
|
UUID: "test-uuid-json",
|
|
Name: "Bad JSON",
|
|
ProviderType: "cloudflare",
|
|
CredentialsEncrypted: ciphertext,
|
|
Enabled: true,
|
|
}
|
|
require.NoError(t, db.Create(provider).Error)
|
|
|
|
_, err = service.GetDecryptedCredentials(ctx, provider.ID)
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrDecryptionFailed)
|
|
}
|
|
|
|
func TestDNSProviderService_UpdateDefaultLogic(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create two providers, first is default
|
|
provider1, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Provider 1",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token1"},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
provider2, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Provider 2",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
"secret_access_key": "secret",
|
|
"region": "us-east-1",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Make provider2 default via update
|
|
isDefault := true
|
|
_, err = service.Update(ctx, provider2.ID, UpdateDNSProviderRequest{
|
|
IsDefault: &isDefault,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify provider1 is no longer default
|
|
updated1, err := service.Get(ctx, provider1.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, updated1.IsDefault)
|
|
|
|
// Verify provider2 is default
|
|
updated2, err := service.Get(ctx, provider2.ID)
|
|
require.NoError(t, err)
|
|
assert.True(t, updated2.IsDefault)
|
|
|
|
// Unset default
|
|
notDefault := false
|
|
_, err = service.Update(ctx, provider2.ID, UpdateDNSProviderRequest{
|
|
IsDefault: ¬Default,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
updated2, err = service.Get(ctx, provider2.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, updated2.IsDefault)
|
|
}
|
|
|
|
func TestAllProviderTypes(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Test all supported provider types
|
|
testCases := map[string]map[string]string{
|
|
"cloudflare": {"api_token": "token"},
|
|
"route53": {"access_key_id": "key", "secret_access_key": "secret", "region": "us-east-1"},
|
|
"digitalocean": {"api_token": "token"},
|
|
"googleclouddns": {"service_account_json": "{}", "project": "test-project"},
|
|
"namecheap": {"api_user": "user", "api_key": "key", "client_ip": "1.2.3.4"},
|
|
"godaddy": {"api_key": "key", "api_secret": "secret"},
|
|
"azure": {
|
|
"tenant_id": "tenant",
|
|
"client_id": "client",
|
|
"client_secret": "secret",
|
|
"subscription_id": "sub",
|
|
"resource_group": "rg",
|
|
},
|
|
"hetzner": {"api_token": "key"},
|
|
"vultr": {"api_key": "key"},
|
|
"dnsimple": {"api_token": "token", "account_id": "12345"},
|
|
}
|
|
|
|
for providerType, creds := range testCases {
|
|
t.Run(providerType, func(t *testing.T) {
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test " + providerType,
|
|
ProviderType: providerType,
|
|
Credentials: creds,
|
|
})
|
|
require.NoError(t, err, "Failed to create %s provider", providerType)
|
|
assert.Equal(t, providerType, provider.ProviderType)
|
|
|
|
// Verify credentials can be decrypted
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, creds, decrypted)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDNSProviderService_UpdateInvalidCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Try to update with invalid credentials
|
|
invalidCreds := map[string]string{"wrong_field": "value"}
|
|
_, err = service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Credentials: invalidCreds,
|
|
})
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrInvalidCredentials)
|
|
}
|
|
|
|
func TestDNSProviderService_CreateEncryptionError(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create with credentials that would marshal to invalid JSON
|
|
// This is hard to test without mocking, so we test the encryption path by
|
|
// verifying that any errors during encryption are properly wrapped
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "valid-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, provider.CredentialsEncrypted)
|
|
}
|
|
|
|
func TestDNSProviderService_Update_PropagationTimeoutAndPollingInterval(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider with default values
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 120, provider.PropagationTimeout)
|
|
assert.Equal(t, 5, provider.PollingInterval)
|
|
|
|
t.Run("update propagation timeout", func(t *testing.T) {
|
|
newTimeout := 300
|
|
updated, err := service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
PropagationTimeout: &newTimeout,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 300, updated.PropagationTimeout)
|
|
})
|
|
|
|
t.Run("update polling interval", func(t *testing.T) {
|
|
newInterval := 10
|
|
updated, err := service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
PollingInterval: &newInterval,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 10, updated.PollingInterval)
|
|
})
|
|
|
|
t.Run("update both timeout and interval", func(t *testing.T) {
|
|
newTimeout := 180
|
|
newInterval := 15
|
|
updated, err := service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
PropagationTimeout: &newTimeout,
|
|
PollingInterval: &newInterval,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 180, updated.PropagationTimeout)
|
|
assert.Equal(t, 15, updated.PollingInterval)
|
|
})
|
|
}
|
|
|
|
func TestDNSProviderService_Test_NonExistentProvider(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Test with non-existent provider
|
|
_, err := service.Test(ctx, 9999)
|
|
assert.ErrorIs(t, err, ErrDNSProviderNotFound)
|
|
}
|
|
|
|
func TestDNSProviderService_GetDecryptedCredentials_NonExistentProvider(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Get credentials for non-existent provider
|
|
_, err := service.GetDecryptedCredentials(ctx, 9999)
|
|
assert.ErrorIs(t, err, ErrDNSProviderNotFound)
|
|
}
|
|
|
|
func TestDNSProviderService_TestWithFailedCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create provider with valid encrypted credentials
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Test should succeed and update success count
|
|
result, err := service.Test(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
|
|
// Verify success count incremented
|
|
updated, err := service.Get(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, updated.SuccessCount)
|
|
assert.Equal(t, 0, updated.FailureCount)
|
|
assert.Empty(t, updated.LastError)
|
|
}
|
|
|
|
func TestDNSProviderService_CreateWithEmptyCredentialValue(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create with empty string value in required field
|
|
_, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": ""},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrInvalidCredentials)
|
|
}
|
|
|
|
func TestDNSProviderService_Update_EmptyCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "original"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Update with empty credentials map (should not update credentials)
|
|
newName := "New Name"
|
|
updated, err := service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Name: &newName,
|
|
Credentials: map[string]string{}, // Empty map
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "New Name", updated.Name)
|
|
|
|
// Verify original credentials preserved
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, updated.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "original", decrypted["api_token"])
|
|
}
|
|
|
|
func TestDNSProviderService_Update_NilCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "original"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Update with nil credentials (should not update credentials)
|
|
newName := "New Name"
|
|
updated, err := service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Name: &newName,
|
|
Credentials: nil,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "New Name", updated.Name)
|
|
|
|
// Verify original credentials preserved
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, updated.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "original", decrypted["api_token"])
|
|
}
|
|
|
|
func TestDNSProviderService_Create_WithExistingDefault(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create first provider as non-default
|
|
provider1, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "First",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token1"},
|
|
IsDefault: false,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.False(t, provider1.IsDefault)
|
|
|
|
// Create second provider as default
|
|
provider2, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Second",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
"secret_access_key": "secret",
|
|
"region": "us-east-1",
|
|
},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, provider2.IsDefault)
|
|
|
|
// Verify first is still non-default
|
|
updated1, err := service.Get(ctx, provider1.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, updated1.IsDefault)
|
|
}
|
|
|
|
func TestDNSProviderService_Delete_AlreadyDeleted(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Delete successfully
|
|
err = service.Delete(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Delete again (already deleted) - should return not found
|
|
err = service.Delete(ctx, provider.ID)
|
|
assert.ErrorIs(t, err, ErrDNSProviderNotFound)
|
|
}
|
|
|
|
func TestTestDNSProviderCredentials_Validation(t *testing.T) {
|
|
// Test the internal testDNSProviderCredentials function
|
|
tests := []struct {
|
|
name string
|
|
providerType string
|
|
credentials map[string]string
|
|
wantSuccess bool
|
|
wantCode string
|
|
}{
|
|
{
|
|
name: "valid cloudflare credentials",
|
|
providerType: "cloudflare",
|
|
credentials: map[string]string{"api_token": "valid-token"},
|
|
wantSuccess: true,
|
|
wantCode: "",
|
|
},
|
|
{
|
|
name: "missing required field",
|
|
providerType: "cloudflare",
|
|
credentials: map[string]string{},
|
|
wantSuccess: false,
|
|
wantCode: "VALIDATION_ERROR",
|
|
},
|
|
{
|
|
name: "empty required field",
|
|
providerType: "route53",
|
|
credentials: map[string]string{"access_key_id": "", "secret_access_key": "secret", "region": "us-east-1"},
|
|
wantSuccess: false,
|
|
wantCode: "VALIDATION_ERROR",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := testDNSProviderCredentials(tt.providerType, tt.credentials)
|
|
assert.Equal(t, tt.wantSuccess, result.Success)
|
|
if !tt.wantSuccess {
|
|
assert.Equal(t, tt.wantCode, result.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDNSProviderService_Update_CredentialValidationError(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a route53 provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Route53",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
"secret_access_key": "secret",
|
|
"region": "us-east-1",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Update with missing required credentials
|
|
_, err = service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Credentials: map[string]string{
|
|
"access_key_id": "new-key",
|
|
// Missing secret_access_key and region
|
|
},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, ErrInvalidCredentials)
|
|
}
|
|
|
|
func TestDNSProviderService_TestCredentials_AllProviders(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Test credentials for all supported provider types without saving
|
|
testCases := map[string]map[string]string{
|
|
"cloudflare": {"api_token": "token"},
|
|
"route53": {"access_key_id": "key", "secret_access_key": "secret", "region": "us-east-1"},
|
|
"digitalocean": {"api_token": "token"},
|
|
"googleclouddns": {"service_account_json": "{}", "project": "test-project"},
|
|
"namecheap": {"api_user": "user", "api_key": "key", "client_ip": "1.2.3.4"},
|
|
"godaddy": {"api_key": "key", "api_secret": "secret"},
|
|
"azure": {
|
|
"tenant_id": "tenant",
|
|
"client_id": "client",
|
|
"client_secret": "secret",
|
|
"subscription_id": "sub",
|
|
"resource_group": "rg",
|
|
},
|
|
"hetzner": {"api_token": "key"},
|
|
"vultr": {"api_key": "key"},
|
|
"dnsimple": {"api_token": "token", "account_id": "12345"},
|
|
}
|
|
|
|
for providerType, creds := range testCases {
|
|
t.Run(providerType, func(t *testing.T) {
|
|
result, err := service.TestCredentials(ctx, CreateDNSProviderRequest{
|
|
Name: "Test " + providerType,
|
|
ProviderType: providerType,
|
|
Credentials: creds,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success, "Provider %s should succeed", providerType)
|
|
assert.NotEmpty(t, result.Message)
|
|
assert.GreaterOrEqual(t, result.PropagationTimeMs, int64(0))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDNSProviderService_List_Empty(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// List on empty database
|
|
providers, err := service.List(ctx)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, providers)
|
|
}
|
|
|
|
func TestDNSProviderService_Create_DefaultsApplied(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create provider without specifying defaults
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
// PropagationTimeout and PollingInterval not set
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify defaults were applied
|
|
assert.Equal(t, 120, provider.PropagationTimeout)
|
|
assert.Equal(t, 5, provider.PollingInterval)
|
|
assert.True(t, provider.Enabled)
|
|
}
|
|
|
|
func TestDNSProviderService_Create_CustomTimeouts(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create provider with custom timeouts
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
PropagationTimeout: 300,
|
|
PollingInterval: 10,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify custom values were used
|
|
assert.Equal(t, 300, provider.PropagationTimeout)
|
|
assert.Equal(t, 10, provider.PollingInterval)
|
|
}
|
|
|
|
func TestDNSProviderService_List_OrderByDefault(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create multiple providers
|
|
_, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "B Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "A Provider",
|
|
ProviderType: "hetzner",
|
|
Credentials: map[string]string{"api_token": "key"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
defaultProvider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Z Default Provider",
|
|
ProviderType: "vultr",
|
|
Credentials: map[string]string{"api_key": "key"},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// List all providers
|
|
providers, err := service.List(ctx)
|
|
require.NoError(t, err)
|
|
assert.Len(t, providers, 3)
|
|
|
|
// Verify default provider is first, then alphabetical order
|
|
assert.Equal(t, defaultProvider.ID, providers[0].ID)
|
|
assert.True(t, providers[0].IsDefault)
|
|
}
|
|
|
|
func TestDNSProviderService_Update_MultipleFields(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Original",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "original-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Update multiple fields at once
|
|
newName := "Updated"
|
|
newTimeout := 240
|
|
newInterval := 8
|
|
enabled := false
|
|
isDefault := true
|
|
|
|
updated, err := service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Name: &newName,
|
|
PropagationTimeout: &newTimeout,
|
|
PollingInterval: &newInterval,
|
|
Enabled: &enabled,
|
|
IsDefault: &isDefault,
|
|
Credentials: map[string]string{"api_token": "new-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "Updated", updated.Name)
|
|
assert.Equal(t, 240, updated.PropagationTimeout)
|
|
assert.Equal(t, 8, updated.PollingInterval)
|
|
assert.False(t, updated.Enabled)
|
|
assert.True(t, updated.IsDefault)
|
|
|
|
// Verify credentials updated
|
|
decrypted, err := service.GetDecryptedCredentials(ctx, updated.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "new-token", decrypted["api_token"])
|
|
}
|
|
|
|
func TestDNSProviderService_GetDecryptedCredentials_UpdatesLastUsed(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify LastUsedAt is initially nil
|
|
initial, err := service.Get(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, initial.LastUsedAt)
|
|
|
|
// Get decrypted credentials
|
|
_, err = service.GetDecryptedCredentials(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Verify LastUsedAt was updated
|
|
afterGet, err := service.Get(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, afterGet.LastUsedAt)
|
|
}
|
|
|
|
func TestDNSProviderService_Test_UpdatesStatistics(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify initial statistics
|
|
initial, err := service.Get(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, initial.SuccessCount)
|
|
assert.Equal(t, 0, initial.FailureCount)
|
|
assert.Nil(t, initial.LastUsedAt)
|
|
assert.Empty(t, initial.LastError)
|
|
|
|
// Test the provider (should succeed with basic validation)
|
|
result, err := service.Test(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
|
|
// Verify statistics updated
|
|
afterTest, err := service.Get(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, afterTest.SuccessCount)
|
|
assert.Equal(t, 0, afterTest.FailureCount)
|
|
assert.NotNil(t, afterTest.LastUsedAt)
|
|
assert.Empty(t, afterTest.LastError)
|
|
}
|
|
|
|
func TestDNSProviderService_Test_FailureUpdatesStatistics(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a cloudflare provider with valid credentials
|
|
cloudflareCredentials := map[string]string{"api_token": "token"}
|
|
credJSON, err := json.Marshal(cloudflareCredentials)
|
|
require.NoError(t, err)
|
|
|
|
encryptedCreds, err := encryptor.Encrypt(credJSON)
|
|
require.NoError(t, err)
|
|
|
|
// Manually insert a provider with mismatched provider type and credentials
|
|
// Provider type is "route53" but credentials are for cloudflare (missing required fields)
|
|
provider := &models.DNSProvider{
|
|
UUID: "test-mismatch-uuid",
|
|
Name: "Mismatched Provider",
|
|
ProviderType: "route53", // Requires access_key_id, secret_access_key, region
|
|
CredentialsEncrypted: encryptedCreds,
|
|
PropagationTimeout: 120,
|
|
PollingInterval: 5,
|
|
Enabled: true,
|
|
}
|
|
require.NoError(t, db.Create(provider).Error)
|
|
|
|
// Test the provider - should fail validation due to mismatched credentials
|
|
result, err := service.Test(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Success)
|
|
assert.Equal(t, "VALIDATION_ERROR", result.Code)
|
|
|
|
// Verify failure statistics updated
|
|
afterTest, err := service.Get(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, afterTest.SuccessCount)
|
|
assert.Equal(t, 1, afterTest.FailureCount)
|
|
assert.NotNil(t, afterTest.LastUsedAt)
|
|
assert.NotEmpty(t, afterTest.LastError)
|
|
}
|
|
|
|
func TestDNSProviderService_List_DBError(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Close the DB connection to trigger error
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
_ = sqlDB.Close()
|
|
|
|
// List should fail
|
|
_, err = service.List(ctx)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestDNSProviderService_Get_DBError(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Close the DB connection to trigger error
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
_ = sqlDB.Close()
|
|
|
|
// Get should fail with a DB error (not ErrDNSProviderNotFound)
|
|
_, err = service.Get(ctx, 1)
|
|
assert.Error(t, err)
|
|
assert.NotErrorIs(t, err, ErrDNSProviderNotFound)
|
|
}
|
|
|
|
func TestDNSProviderService_Create_DBErrorOnDefaultUnset(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
ctx := context.Background()
|
|
|
|
// First, create a default provider with a working DB
|
|
workingService := NewDNSProviderService(db, encryptor)
|
|
_, err := workingService.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "First Default",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Now close the DB
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
_ = sqlDB.Close()
|
|
|
|
// Trying to create another default should fail when trying to unset the existing default
|
|
_, err = workingService.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Second Default",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token2"},
|
|
IsDefault: true,
|
|
})
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestDNSProviderService_Create_DBErrorOnCreate(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Close the DB connection to trigger error
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
_ = sqlDB.Close()
|
|
|
|
// Create should fail
|
|
_, err = service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestDNSProviderService_Update_DBErrorOnSave(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create a provider first
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Close the DB connection to trigger error
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
_ = sqlDB.Close()
|
|
|
|
// Update should fail
|
|
newName := "Updated"
|
|
_, err = service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Name: &newName,
|
|
})
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestDNSProviderService_Update_DBErrorOnDefaultUnset(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Create two providers, first is default
|
|
_, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "First",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "token1"},
|
|
IsDefault: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
provider2, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Second",
|
|
ProviderType: "route53",
|
|
Credentials: map[string]string{
|
|
"access_key_id": "key",
|
|
"secret_access_key": "secret",
|
|
"region": "us-east-1",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Close the DB connection to trigger error
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
_ = sqlDB.Close()
|
|
|
|
// Update to make second provider default should fail
|
|
isDefault := true
|
|
_, err = service.Update(ctx, provider2.ID, UpdateDNSProviderRequest{
|
|
IsDefault: &isDefault,
|
|
})
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestDNSProviderService_Delete_DBError(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.Background()
|
|
|
|
// Close the DB connection to trigger error
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
_ = sqlDB.Close()
|
|
|
|
// Delete should fail
|
|
err = service.Delete(ctx, 1)
|
|
assert.Error(t, err)
|
|
assert.NotErrorIs(t, err, ErrDNSProviderNotFound)
|
|
}
|
|
|
|
func TestDNSProviderService_AuditLogging_Create(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
// Also migrate SecurityAudit model for audit logging
|
|
err := db.AutoMigrate(&models.SecurityAudit{})
|
|
require.NoError(t, err)
|
|
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.WithValue(context.Background(), testUserIDKey, "test-user")
|
|
ctx = context.WithValue(ctx, testClientIPKey, "192.168.1.1")
|
|
ctx = context.WithValue(ctx, testUserAgentKey, "TestAgent/1.0")
|
|
|
|
// Create a provider
|
|
req := CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{
|
|
"api_token": "test-token",
|
|
},
|
|
IsDefault: true,
|
|
}
|
|
|
|
provider, err := service.Create(ctx, req)
|
|
require.NoError(t, err)
|
|
|
|
// Give time for async audit logging
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Verify audit log was created
|
|
var audit models.SecurityAudit
|
|
err = db.Where("action = ? AND event_category = ?", "dns_provider_create", "dns_provider").First(&audit).Error
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "test-user", audit.Actor)
|
|
assert.Equal(t, "dns_provider_create", audit.Action)
|
|
assert.Equal(t, "dns_provider", audit.EventCategory)
|
|
assert.Equal(t, provider.UUID, audit.ResourceUUID)
|
|
assert.Equal(t, "192.168.1.1", audit.IPAddress)
|
|
assert.Equal(t, "TestAgent/1.0", audit.UserAgent)
|
|
|
|
// Verify details contain expected fields
|
|
var details map[string]interface{}
|
|
err = json.Unmarshal([]byte(audit.Details), &details)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Test Provider", details["name"])
|
|
assert.Equal(t, "cloudflare", details["type"])
|
|
assert.True(t, details["is_default"].(bool))
|
|
}
|
|
|
|
func TestDNSProviderService_AuditLogging_Update(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.WithValue(context.Background(), testUserIDKey, "test-user")
|
|
ctx = context.WithValue(ctx, testClientIPKey, "192.168.1.2")
|
|
ctx = context.WithValue(ctx, testUserAgentKey, "TestAgent/1.0")
|
|
|
|
// Create a provider first
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Original Name",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "test-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for create audit to be processed
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Clear any create audit logs
|
|
db.Exec("DELETE FROM security_audits")
|
|
|
|
// Update the provider
|
|
newName := "Updated Name"
|
|
enabled := false
|
|
_, err = service.Update(ctx, provider.ID, UpdateDNSProviderRequest{
|
|
Name: &newName,
|
|
Enabled: &enabled,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Give time for async audit logging
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Verify audit log was created
|
|
var audit models.SecurityAudit
|
|
err = db.Where("action = ? AND event_category = ?", "dns_provider_update", "dns_provider").First(&audit).Error
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "test-user", audit.Actor)
|
|
assert.Equal(t, provider.UUID, audit.ResourceUUID)
|
|
|
|
// Verify details contain changed fields
|
|
var details map[string]interface{}
|
|
err = json.Unmarshal([]byte(audit.Details), &details)
|
|
require.NoError(t, err)
|
|
|
|
changedFields := details["changed_fields"].(map[string]interface{})
|
|
assert.True(t, changedFields["name"].(bool))
|
|
assert.True(t, changedFields["enabled"].(bool))
|
|
|
|
oldValues := details["old_values"].(map[string]interface{})
|
|
assert.Equal(t, "Original Name", oldValues["name"])
|
|
|
|
newValues := details["new_values"].(map[string]interface{})
|
|
assert.Equal(t, "Updated Name", newValues["name"])
|
|
}
|
|
|
|
func TestDNSProviderService_AuditLogging_Delete(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.WithValue(context.Background(), testUserIDKey, "admin-user")
|
|
ctx = context.WithValue(ctx, testClientIPKey, "10.0.0.1")
|
|
ctx = context.WithValue(ctx, testUserAgentKey, "TestAgent/1.0")
|
|
|
|
// Create a provider first
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "To Be Deleted",
|
|
ProviderType: "digitalocean",
|
|
Credentials: map[string]string{"api_token": "test-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for create audit to be processed
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Clear create audit logs
|
|
db.Exec("DELETE FROM security_audits")
|
|
|
|
// Delete the provider
|
|
err = service.Delete(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Give time for async audit logging
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Verify audit log was created
|
|
var audit models.SecurityAudit
|
|
err = db.Where("action = ? AND event_category = ?", "dns_provider_delete", "dns_provider").First(&audit).Error
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "admin-user", audit.Actor)
|
|
assert.Equal(t, provider.UUID, audit.ResourceUUID)
|
|
assert.Equal(t, "10.0.0.1", audit.IPAddress)
|
|
|
|
// Verify details
|
|
var details map[string]interface{}
|
|
err = json.Unmarshal([]byte(audit.Details), &details)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "To Be Deleted", details["name"])
|
|
assert.Equal(t, "digitalocean", details["type"])
|
|
assert.True(t, details["had_credentials"].(bool))
|
|
}
|
|
|
|
func TestDNSProviderService_AuditLogging_Test(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.WithValue(context.Background(), testUserIDKey, "test-user")
|
|
ctx = context.WithValue(ctx, testClientIPKey, "192.168.1.1")
|
|
ctx = context.WithValue(ctx, testUserAgentKey, "TestAgent/1.0")
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "test-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for create audit to be processed
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Clear create audit logs
|
|
db.Exec("DELETE FROM security_audits")
|
|
|
|
// Test the provider
|
|
_, err = service.Test(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Give time for async audit logging
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Verify audit log was created
|
|
var audit models.SecurityAudit
|
|
err = db.Where("action = ? AND event_category = ?", "credential_test", "dns_provider").First(&audit).Error
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "test-user", audit.Actor)
|
|
assert.Equal(t, provider.UUID, audit.ResourceUUID)
|
|
}
|
|
|
|
func TestDNSProviderService_AuditLogging_GetDecryptedCredentials(t *testing.T) {
|
|
db, encryptor := setupDNSProviderTestDB(t)
|
|
service := NewDNSProviderService(db, encryptor)
|
|
ctx := context.WithValue(context.Background(), testUserIDKey, "admin")
|
|
ctx = context.WithValue(ctx, testClientIPKey, "192.168.1.1")
|
|
ctx = context.WithValue(ctx, testUserAgentKey, "TestAgent/1.0")
|
|
|
|
// Create a provider
|
|
provider, err := service.Create(ctx, CreateDNSProviderRequest{
|
|
Name: "Test Provider",
|
|
ProviderType: "cloudflare",
|
|
Credentials: map[string]string{"api_token": "secret-token"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for create audit to be processed
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Clear create audit logs
|
|
db.Exec("DELETE FROM security_audits")
|
|
|
|
// Get decrypted credentials
|
|
_, err = service.GetDecryptedCredentials(ctx, provider.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Give time for async audit logging
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Verify audit log was created
|
|
var audit models.SecurityAudit
|
|
err = db.Where("action = ? AND event_category = ?", "credential_decrypt", "dns_provider").First(&audit).Error
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "admin", audit.Actor)
|
|
assert.Equal(t, provider.UUID, audit.ResourceUUID)
|
|
|
|
// Verify details
|
|
var details map[string]interface{}
|
|
err = json.Unmarshal([]byte(audit.Details), &details)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "credentials_access", details["purpose"])
|
|
assert.True(t, details["success"].(bool))
|
|
}
|
|
|
|
func TestDNSProviderService_AuditLogging_ContextHelpers(t *testing.T) {
|
|
// Test actor extraction
|
|
ctx := context.WithValue(context.Background(), testUserIDKey, "user-123")
|
|
actor := getActorFromContext(ctx)
|
|
assert.Equal(t, "user-123", actor)
|
|
|
|
// Test with uint user ID
|
|
ctx = context.WithValue(context.Background(), testUserIDKey, uint(456))
|
|
actor = getActorFromContext(ctx)
|
|
assert.Equal(t, "456", actor)
|
|
|
|
// Test without user ID (should default to "system")
|
|
ctx = context.Background()
|
|
actor = getActorFromContext(ctx)
|
|
assert.Equal(t, "system", actor)
|
|
|
|
// Test IP extraction
|
|
ctx = context.WithValue(context.Background(), testClientIPKey, "10.0.0.1")
|
|
ip := getIPFromContext(ctx)
|
|
assert.Equal(t, "10.0.0.1", ip)
|
|
|
|
// Test User-Agent extraction
|
|
ctx = context.WithValue(context.Background(), testUserAgentKey, "TestAgent/2.0")
|
|
ua := getUserAgentFromContext(ctx)
|
|
assert.Equal(t, "TestAgent/2.0", ua)
|
|
}
|