Files
Charon/backend/internal/api/handlers/pr_coverage_test.go
GitHub Actions 3169b05156 fix: skip incomplete system log viewer tests
- Marked 12 tests as skip pending feature implementation
- Features tracked in GitHub issue #686 (system log viewer feature completion)
- Tests cover sorting by timestamp/level/method/URI/status, pagination controls, filtering by text/level, download functionality
- Unblocks Phase 2 at 91.7% pass rate to proceed to Phase 3 security enforcement validation
- TODO comments in code reference GitHub #686 for feature completion tracking
- Tests skipped: Pagination (3), Search/Filter (2), Download (2), Sorting (1), Log Display (4)
2026-02-09 21:55:55 +00:00

843 lines
26 KiB
Go

package handlers
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"github.com/Wikid82/charon/backend/internal/crypto"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/Wikid82/charon/backend/internal/services"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// =============================================================================
// Additional Plugin Handler Tests for Coverage
// =============================================================================
func TestPluginHandler_EnablePlugin_DatabaseUpdateError(t *testing.T) {
gin.SetMode(gin.TestMode)
db := OpenTestDBWithMigrations(t)
pluginLoader := services.NewPluginLoaderService(db, "/tmp/plugins", nil)
// Create plugin
plugin := models.Plugin{
UUID: "plugin-db-error-uuid",
Name: "Test Plugin",
Type: "test-type",
Enabled: false,
Status: models.PluginStatusError,
FilePath: "/nonexistent/path.so",
}
db.Create(&plugin)
handler := NewPluginHandler(db, pluginLoader)
// Close DB to trigger error during update
sqlDB, _ := db.DB()
_ = sqlDB.Close()
router := gin.New()
router.POST("/plugins/:id/enable", handler.EnablePlugin)
req := httptest.NewRequest(http.MethodPost, "/plugins/1/enable", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestPluginHandler_DisablePlugin_DatabaseUpdateError(t *testing.T) {
gin.SetMode(gin.TestMode)
db := OpenTestDBWithMigrations(t)
pluginLoader := services.NewPluginLoaderService(db, "/tmp/plugins", nil)
// Create plugin
plugin := models.Plugin{
UUID: "plugin-disable-error-uuid",
Name: "Test Plugin",
Type: "test-type-disable",
Enabled: true,
Status: models.PluginStatusLoaded,
FilePath: "/path/to/plugin.so",
}
db.Create(&plugin)
handler := NewPluginHandler(db, pluginLoader)
// Close DB to trigger error during update
sqlDB, _ := db.DB()
_ = sqlDB.Close()
router := gin.New()
router.POST("/plugins/:id/disable", handler.DisablePlugin)
req := httptest.NewRequest(http.MethodPost, "/plugins/1/disable", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestPluginHandler_GetPlugin_DatabaseError(t *testing.T) {
gin.SetMode(gin.TestMode)
db := OpenTestDBWithMigrations(t)
pluginLoader := services.NewPluginLoaderService(db, "/tmp/plugins", nil)
// Create plugin first
plugin := models.Plugin{
UUID: "get-error-uuid",
Name: "Get Error",
Type: "get-error-type",
Enabled: true,
FilePath: "/path/to/get.so",
}
db.Create(&plugin)
handler := NewPluginHandler(db, pluginLoader)
// Close DB to trigger database error
sqlDB, _ := db.DB()
_ = sqlDB.Close()
router := gin.New()
router.GET("/plugins/:id", handler.GetPlugin)
req := httptest.NewRequest(http.MethodGet, "/plugins/1", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, w.Body.String(), "Failed to get plugin")
}
func TestPluginHandler_EnablePlugin_DatabaseFirstError(t *testing.T) {
gin.SetMode(gin.TestMode)
db := OpenTestDBWithMigrations(t)
pluginLoader := services.NewPluginLoaderService(db, "/tmp/plugins", nil)
handler := NewPluginHandler(db, pluginLoader)
// Close DB to trigger error when fetching plugin
sqlDB, _ := db.DB()
_ = sqlDB.Close()
router := gin.New()
router.POST("/plugins/:id/enable", handler.EnablePlugin)
req := httptest.NewRequest(http.MethodPost, "/plugins/1/enable", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, w.Body.String(), "Failed to get plugin")
}
func TestPluginHandler_DisablePlugin_DatabaseFirstError(t *testing.T) {
gin.SetMode(gin.TestMode)
db := OpenTestDBWithMigrations(t)
pluginLoader := services.NewPluginLoaderService(db, "/tmp/plugins", nil)
handler := NewPluginHandler(db, pluginLoader)
// Close DB to trigger error when fetching plugin
sqlDB, _ := db.DB()
_ = sqlDB.Close()
router := gin.New()
router.POST("/plugins/:id/disable", handler.DisablePlugin)
req := httptest.NewRequest(http.MethodPost, "/plugins/1/disable", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, w.Body.String(), "Failed to get plugin")
}
// =============================================================================
// Encryption Handler - Additional Coverage Tests
// =============================================================================
func TestEncryptionHandler_Validate_NonAdminAccess(t *testing.T) {
gin.SetMode(gin.TestMode)
currentKey, _ := crypto.GenerateNewKey()
require.NoError(t, os.Setenv("CHARON_ENCRYPTION_KEY", currentKey))
defer func() { require.NoError(t, os.Unsetenv("CHARON_ENCRYPTION_KEY")) }()
db := setupEncryptionTestDB(t)
rotationService, _ := crypto.NewRotationService(db)
securityService := services.NewSecurityService(db)
defer securityService.Close()
handler := NewEncryptionHandler(rotationService, securityService)
router := setupEncryptionTestRouter(handler, false)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/admin/encryption/validate", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusForbidden, w.Code)
}
func TestEncryptionHandler_GetHistory_PaginationBoundary(t *testing.T) {
gin.SetMode(gin.TestMode)
currentKey, _ := crypto.GenerateNewKey()
require.NoError(t, os.Setenv("CHARON_ENCRYPTION_KEY", currentKey))
defer func() { require.NoError(t, os.Unsetenv("CHARON_ENCRYPTION_KEY")) }()
db := setupEncryptionTestDB(t)
rotationService, _ := crypto.NewRotationService(db)
securityService := services.NewSecurityService(db)
defer securityService.Close()
handler := NewEncryptionHandler(rotationService, securityService)
router := setupEncryptionTestRouter(handler, true)
// Test invalid page number (negative)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/admin/encryption/history?page=-1&limit=10", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
// Test limit exceeding max (should clamp)
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/api/v1/admin/encryption/history?page=1&limit=200", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
_ = json.Unmarshal(w.Body.Bytes(), &response)
// limit should not exceed 100
assert.LessOrEqual(t, response["limit"].(float64), float64(100))
}
func TestEncryptionHandler_GetStatus_VersionInfo(t *testing.T) {
gin.SetMode(gin.TestMode)
currentKey, _ := crypto.GenerateNewKey()
require.NoError(t, os.Setenv("CHARON_ENCRYPTION_KEY", currentKey))
defer func() {
require.NoError(t, os.Unsetenv("CHARON_ENCRYPTION_KEY"))
}()
db := setupEncryptionTestDB(t)
rotationService, _ := crypto.NewRotationService(db)
securityService := services.NewSecurityService(db)
defer securityService.Close()
handler := NewEncryptionHandler(rotationService, securityService)
router := setupEncryptionTestRouter(handler, true)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/admin/encryption/status", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var status crypto.RotationStatus
err := json.Unmarshal(w.Body.Bytes(), &status)
assert.NoError(t, err)
// Verify the status response has expected fields
assert.True(t, status.CurrentVersion >= 1)
}
// =============================================================================
// Settings Handler - Additional Unique Coverage Tests
// =============================================================================
func TestSettingsHandler_TestPublicURL_RoleNotExists(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
handler := NewSettingsHandler(db)
router := gin.New()
// Don't set any role
router.POST("/test-url", handler.TestPublicURL)
body := `{"url": "https://example.com"}`
req, _ := http.NewRequest("POST", "/test-url", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusForbidden, w.Code)
}
func TestSettingsHandler_TestPublicURL_InvalidURLFormat(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
handler := NewSettingsHandler(db)
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("role", "admin")
c.Next()
})
router.POST("/test-url", handler.TestPublicURL)
body := `{"url": "not-a-valid-url"}`
req, _ := http.NewRequest("POST", "/test-url", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestSettingsHandler_TestPublicURL_PrivateIPBlocked_Coverage(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
handler := NewSettingsHandler(db)
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("role", "admin")
c.Next()
})
router.POST("/test-url", handler.TestPublicURL)
// SSRF attempt with private IP
body := `{"url": "http://192.168.1.1"}`
req, _ := http.NewRequest("POST", "/test-url", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Should return 200 but with reachable=false due to SSRF protection
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
_ = json.Unmarshal(w.Body.Bytes(), &response)
assert.False(t, response["reachable"].(bool))
}
func TestSettingsHandler_ValidatePublicURL_WithTrailingSlash(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
handler := NewSettingsHandler(db)
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("role", "admin")
c.Next()
})
router.POST("/validate-url", handler.ValidatePublicURL)
// URL with trailing slash (should normalize and may produce warning)
body := `{"url": "https://example.com/"}`
req, _ := http.NewRequest("POST", "/validate-url", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
_ = json.Unmarshal(w.Body.Bytes(), &response)
assert.True(t, response["valid"].(bool))
}
func TestSettingsHandler_ValidatePublicURL_MissingScheme(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
handler := NewSettingsHandler(db)
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("role", "admin")
c.Next()
})
router.POST("/validate-url", handler.ValidatePublicURL)
// Invalid URL (missing scheme)
body := `{"url": "example.com"}`
req, _ := http.NewRequest("POST", "/validate-url", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
var response map[string]any
_ = json.Unmarshal(w.Body.Bytes(), &response)
assert.False(t, response["valid"].(bool))
}
// =============================================================================
// Audit Log Handler - Additional Coverage Tests
// =============================================================================
func TestAuditLogHandler_List_PaginationEdgeCases(t *testing.T) {
gin.SetMode(gin.TestMode)
dbPath := fmt.Sprintf("/tmp/test_audit_pagination_%d.db", time.Now().UnixNano())
t.Cleanup(func() { _ = os.Remove(dbPath) })
db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
_ = db.AutoMigrate(&models.SecurityAudit{}, &models.DNSProvider{})
// Create test audits
for i := 0; i < 10; i++ {
db.Create(&models.SecurityAudit{
Actor: "user1",
Action: fmt.Sprintf("action_%d", i),
EventCategory: "test",
Details: "{}",
})
}
secService := services.NewSecurityService(db)
defer secService.Close()
handler := NewAuditLogHandler(secService)
router := gin.New()
router.GET("/audit", handler.List)
// Test with pagination
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/audit?page=2&limit=3", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestAuditLogHandler_List_CategoryFilter(t *testing.T) {
gin.SetMode(gin.TestMode)
dbPath := fmt.Sprintf("/tmp/test_audit_category_%d.db", time.Now().UnixNano())
t.Cleanup(func() { _ = os.Remove(dbPath) })
db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
_ = db.AutoMigrate(&models.SecurityAudit{}, &models.DNSProvider{})
// Create test audits with different categories
db.Create(&models.SecurityAudit{
Actor: "user1",
Action: "action1",
EventCategory: "encryption",
Details: "{}",
})
db.Create(&models.SecurityAudit{
Actor: "user2",
Action: "action2",
EventCategory: "security",
Details: "{}",
})
secService := services.NewSecurityService(db)
defer secService.Close()
handler := NewAuditLogHandler(secService)
router := gin.New()
router.GET("/audit", handler.List)
// Test with category filter
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/audit?category=encryption", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestAuditLogHandler_ListByProvider_DatabaseError(t *testing.T) {
gin.SetMode(gin.TestMode)
dbPath := fmt.Sprintf("/tmp/test_audit_db_error_%d.db", time.Now().UnixNano())
t.Cleanup(func() { _ = os.Remove(dbPath) })
db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
_ = db.AutoMigrate(&models.SecurityAudit{}, &models.DNSProvider{})
secService := services.NewSecurityService(db)
defer secService.Close()
handler := NewAuditLogHandler(secService)
// Close DB to trigger error
sqlDB, _ := db.DB()
_ = sqlDB.Close()
router := gin.New()
router.GET("/audit/provider/:id", handler.ListByProvider)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/audit/provider/1", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestAuditLogHandler_ListByProvider_InvalidProviderID(t *testing.T) {
gin.SetMode(gin.TestMode)
dbPath := fmt.Sprintf("/tmp/test_audit_invalid_id_%d.db", time.Now().UnixNano())
t.Cleanup(func() { _ = os.Remove(dbPath) })
db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
_ = db.AutoMigrate(&models.SecurityAudit{}, &models.DNSProvider{})
secService := services.NewSecurityService(db)
defer secService.Close()
handler := NewAuditLogHandler(secService)
router := gin.New()
router.GET("/audit/provider/:id", handler.ListByProvider)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/audit/provider/invalid", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// =============================================================================
// getActorFromGinContext Additional Coverage
// =============================================================================
func TestGetActorFromGinContext_InvalidUserIDType(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
var capturedActor string
router.Use(func(c *gin.Context) {
c.Set("user_id", 123.45) // float - invalid type
c.Next()
})
router.GET("/test", func(c *gin.Context) {
capturedActor = getActorFromGinContext(c)
c.Status(http.StatusOK)
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/test", nil)
router.ServeHTTP(w, req)
// Should fall back to "system" for invalid type
assert.Equal(t, "system", capturedActor)
}
// =============================================================================
// isAdmin Additional Coverage
// =============================================================================
func TestIsAdmin_NonAdminRole(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("user_role", "user") // Not admin
c.Next()
})
router.GET("/test", func(c *gin.Context) {
if isAdmin(c) {
c.JSON(http.StatusOK, gin.H{"admin": true})
} else {
c.JSON(http.StatusForbidden, gin.H{"admin": false})
}
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/test", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusForbidden, w.Code)
}
// =============================================================================
// Credential Handler - Additional Coverage Tests
// =============================================================================
func setupCredentialHandlerTestWithCtx(t *testing.T) (*gin.Engine, *gorm.DB, *models.DNSProvider, context.Context) {
require.NoError(t, os.Setenv("CHARON_ENCRYPTION_KEY", "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="))
t.Cleanup(func() { require.NoError(t, os.Unsetenv("CHARON_ENCRYPTION_KEY")) })
gin.SetMode(gin.TestMode)
router := gin.New()
dbName := fmt.Sprintf("file:%s?mode=memory&cache=shared&_journal_mode=WAL", t.Name())
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
require.NoError(t, err)
t.Cleanup(func() {
sqlDB, _ := db.DB()
_ = sqlDB.Close()
})
err = db.AutoMigrate(
&models.DNSProvider{},
&models.DNSProviderCredential{},
&models.SecurityAudit{},
)
require.NoError(t, err)
testKey := "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="
encryptor, _ := crypto.NewEncryptionService(testKey)
creds := map[string]string{"api_token": "test-token"}
credsJSON, _ := json.Marshal(creds)
encrypted, _ := encryptor.Encrypt(credsJSON)
provider := &models.DNSProvider{
UUID: "test-uuid",
Name: "Test Provider",
ProviderType: "cloudflare",
Enabled: true,
UseMultiCredentials: true,
CredentialsEncrypted: encrypted,
KeyVersion: 1,
}
db.Create(provider)
credService := services.NewCredentialService(db, encryptor)
credHandler := NewCredentialHandler(credService)
router.GET("/api/v1/dns-providers/:id/credentials", credHandler.List)
router.POST("/api/v1/dns-providers/:id/credentials", credHandler.Create)
router.GET("/api/v1/dns-providers/:id/credentials/:cred_id", credHandler.Get)
router.PUT("/api/v1/dns-providers/:id/credentials/:cred_id", credHandler.Update)
router.DELETE("/api/v1/dns-providers/:id/credentials/:cred_id", credHandler.Delete)
router.POST("/api/v1/dns-providers/:id/credentials/:cred_id/test", credHandler.Test)
router.POST("/api/v1/dns-providers/:id/enable-multi-credentials", credHandler.EnableMultiCredentials)
return router, db, provider, context.Background()
}
func TestCredentialHandler_Update_InvalidProviderType(t *testing.T) {
router, db, _, _ := setupCredentialHandlerTestWithCtx(t)
testKey := "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="
encryptor, _ := crypto.NewEncryptionService(testKey)
// Create provider with invalid type
creds := map[string]string{"api_token": "test-token"}
credsJSON, _ := json.Marshal(creds)
encrypted, _ := encryptor.Encrypt(credsJSON)
provider := &models.DNSProvider{
UUID: "invalid-type-uuid",
Name: "Invalid Type Provider",
ProviderType: "nonexistent-provider",
Enabled: true,
UseMultiCredentials: true,
CredentialsEncrypted: encrypted,
KeyVersion: 1,
}
db.Create(provider)
// Create credential
credService := services.NewCredentialService(db, encryptor)
createReq := services.CreateCredentialRequest{
Label: "Original",
Credentials: map[string]string{"api_token": "token"},
}
// This should fail because provider type doesn't exist
_, err := credService.Create(context.Background(), provider.ID, createReq)
if err != nil {
// Expected - provider type validation fails
return
}
// If it didn't fail, try update with bad credentials
updateBody := `{"label":"Updated","credentials":{"invalid_field":"value"}}`
url := fmt.Sprintf("/api/v1/dns-providers/%d/credentials/1", provider.ID)
req, _ := http.NewRequest("PUT", url, strings.NewReader(updateBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestCredentialHandler_List_DatabaseClosed(t *testing.T) {
require.NoError(t, os.Setenv("CHARON_ENCRYPTION_KEY", "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="))
defer func() { require.NoError(t, os.Unsetenv("CHARON_ENCRYPTION_KEY")) }()
gin.SetMode(gin.TestMode)
router := gin.New()
dbName := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
db, _ := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
_ = db.AutoMigrate(&models.DNSProvider{}, &models.DNSProviderCredential{})
testKey := "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="
encryptor, _ := crypto.NewEncryptionService(testKey)
credService := services.NewCredentialService(db, encryptor)
credHandler := NewCredentialHandler(credService)
router.GET("/api/v1/dns-providers/:id/credentials", credHandler.List)
// Close DB to trigger error
sqlDB, _ := db.DB()
_ = sqlDB.Close()
req, _ := http.NewRequest("GET", "/api/v1/dns-providers/1/credentials", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
// =============================================================================
// Settings Handler - MaskPasswordForTest Coverage (unique test name)
// =============================================================================
func TestSettingsHandler_MaskPasswordForTestFunction(t *testing.T) {
tests := []struct {
name string
password string
expected string
}{
{"empty string", "", ""},
{"non-empty password", "secret123", "********"},
{"already masked", "********", "********"},
{"single char", "x", "********"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := MaskPasswordForTest(tt.password)
assert.Equal(t, tt.expected, result)
})
}
}
// =============================================================================
// Credential Handler - Additional Update/Delete Error Paths (unique names)
// =============================================================================
func TestCredentialHandler_Update_NotFoundError(t *testing.T) {
router, _, provider, _ := setupCredentialHandlerTestWithCtx(t)
updateBody := `{"label":"Updated","credentials":{"api_token":"new-token"}}`
url := fmt.Sprintf("/api/v1/dns-providers/%d/credentials/9999", provider.ID)
req, _ := http.NewRequest("PUT", url, strings.NewReader(updateBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
assert.Contains(t, w.Body.String(), "not found")
}
func TestCredentialHandler_Update_MalformedJSON(t *testing.T) {
router, _, provider, _ := setupCredentialHandlerTestWithCtx(t)
url := fmt.Sprintf("/api/v1/dns-providers/%d/credentials/1", provider.ID)
req, _ := http.NewRequest("PUT", url, strings.NewReader("invalid json"))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestCredentialHandler_Update_BadCredentialID(t *testing.T) {
router, _, provider, _ := setupCredentialHandlerTestWithCtx(t)
url := fmt.Sprintf("/api/v1/dns-providers/%d/credentials/invalid", provider.ID)
req, _ := http.NewRequest("PUT", url, strings.NewReader(`{}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "Invalid credential ID")
}
func TestCredentialHandler_Delete_NotFoundError(t *testing.T) {
router, _, provider, _ := setupCredentialHandlerTestWithCtx(t)
url := fmt.Sprintf("/api/v1/dns-providers/%d/credentials/9999", provider.ID)
req, _ := http.NewRequest("DELETE", url, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestCredentialHandler_Delete_BadCredentialID(t *testing.T) {
router, _, provider, _ := setupCredentialHandlerTestWithCtx(t)
url := fmt.Sprintf("/api/v1/dns-providers/%d/credentials/invalid", provider.ID)
req, _ := http.NewRequest("DELETE", url, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestCredentialHandler_Test_BadCredentialID(t *testing.T) {
router, _, provider, _ := setupCredentialHandlerTestWithCtx(t)
url := fmt.Sprintf("/api/v1/dns-providers/%d/credentials/invalid/test", provider.ID)
req, _ := http.NewRequest("POST", url, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestCredentialHandler_EnableMultiCredentials_BadProviderID(t *testing.T) {
router, _, _, _ := setupCredentialHandlerTestWithCtx(t)
req, _ := http.NewRequest("POST", "/api/v1/dns-providers/invalid/enable-multi-credentials", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// =============================================================================
// Encryption Handler - Additional Validate Success Test
// =============================================================================
func TestEncryptionHandler_Validate_AdminSuccess(t *testing.T) {
gin.SetMode(gin.TestMode)
currentKey, _ := crypto.GenerateNewKey()
require.NoError(t, os.Setenv("CHARON_ENCRYPTION_KEY", currentKey))
defer func() { require.NoError(t, os.Unsetenv("CHARON_ENCRYPTION_KEY")) }()
db := setupEncryptionTestDB(t)
rotationService, _ := crypto.NewRotationService(db)
securityService := services.NewSecurityService(db)
defer securityService.Close()
handler := NewEncryptionHandler(rotationService, securityService)
router := setupEncryptionTestRouter(handler, true)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/admin/encryption/validate", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}