4d00af75b6
- Implemented a function to create a valid SQLite database for testing in db_health_handler_test.go. - Replaced dummy database file creation with a proper SQLite setup to ensure tests run against a valid database. - Set CHARON_ENCRYPTION_KEY environment variable in dns_provider_service_test.go to prevent RotationService initialization warnings. - Added detailed remediation plan for CI Codecov backend test failures, addressing encryption key requirements and database record not found errors.
1818 lines
54 KiB
Go
1818 lines
54 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"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
|
|
)
|
|
|
|
// Use the contextKey type and constants from dns_provider_service.go:
|
|
// - contextKeyUserID
|
|
// - contextKeyClientIP
|
|
// - contextKeyUserAgent
|
|
|
|
// setupTestDB creates an in-memory SQLite database for testing.
|
|
func setupDNSProviderTestDB(t *testing.T) (*gorm.DB, *crypto.EncryptionService) {
|
|
t.Helper()
|
|
|
|
// Set encryption key in environment for RotationService
|
|
// This must match the test key used below to avoid decryption errors
|
|
testKey := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // 32-byte key in base64
|
|
_ = os.Setenv("CHARON_ENCRYPTION_KEY", testKey)
|
|
t.Cleanup(func() { _ = os.Unsetenv("CHARON_ENCRYPTION_KEY") })
|
|
|
|
// 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.Where("id = ?", provider.ID).First(&dbProvider).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.Where("id = ?", provider.ID).First(&retrieved).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 during validation due to invalid 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(), contextKeyUserID, "test-user")
|
|
ctx = context.WithValue(ctx, contextKeyClientIP, "192.168.1.1")
|
|
ctx = context.WithValue(ctx, contextKeyUserAgent, "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(), contextKeyUserID, "test-user")
|
|
ctx = context.WithValue(ctx, contextKeyClientIP, "192.168.1.2")
|
|
ctx = context.WithValue(ctx, contextKeyUserAgent, "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(), contextKeyUserID, "admin-user")
|
|
ctx = context.WithValue(ctx, contextKeyClientIP, "10.0.0.1")
|
|
ctx = context.WithValue(ctx, contextKeyUserAgent, "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(), contextKeyUserID, "test-user")
|
|
ctx = context.WithValue(ctx, contextKeyClientIP, "192.168.1.1")
|
|
ctx = context.WithValue(ctx, contextKeyUserAgent, "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(), contextKeyUserID, "admin")
|
|
ctx = context.WithValue(ctx, contextKeyClientIP, "192.168.1.1")
|
|
ctx = context.WithValue(ctx, contextKeyUserAgent, "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(), contextKeyUserID, "user-123")
|
|
actor := getActorFromContext(ctx)
|
|
assert.Equal(t, "user-123", actor)
|
|
|
|
// Test with uint user ID
|
|
ctx = context.WithValue(context.Background(), contextKeyUserID, 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(), contextKeyClientIP, "10.0.0.1")
|
|
ip := getIPFromContext(ctx)
|
|
assert.Equal(t, "10.0.0.1", ip)
|
|
|
|
// Test User-Agent extraction
|
|
ctx = context.WithValue(context.Background(), contextKeyUserAgent, "TestAgent/2.0")
|
|
ua := getUserAgentFromContext(ctx)
|
|
assert.Equal(t, "TestAgent/2.0", ua)
|
|
}
|