feat: Implement error handling framework (Task 4)
- Add AppError with error codes and hints system - Implement ConnectionError for SSH-specific errors - Add HandleSSHError for intelligent SSH error parsing - Add FormatConnectionError for user-friendly output - All 5 test functions passing (TestAppError, TestConnectionError, TestHandleSSHError, TestHandleSSHErrorNil, TestFormatConnectionError) - Update PROJECT_STATE.md to reflect Task 4 completion Progress: Tasks 1-4 complete (~30%)
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package errors_test
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
apperrors "git.tukangketik.id/swanadiva/hostkeeper/internal/errors"
|
||||
)
|
||||
|
||||
func TestAppError(t *testing.T) {
|
||||
cause := stderrors.New("underlying issue")
|
||||
appErr := apperrors.NewAppError(
|
||||
apperrors.ErrAuthFailed,
|
||||
"Authentication failed",
|
||||
cause,
|
||||
[]string{"Check credentials", "Verify key permissions"},
|
||||
)
|
||||
|
||||
if appErr.Code != apperrors.ErrAuthFailed {
|
||||
t.Errorf("Expected code '%s', got '%s'", apperrors.ErrAuthFailed, appErr.Code)
|
||||
}
|
||||
|
||||
if len(appErr.Hints) != 2 {
|
||||
t.Errorf("Expected 2 hints, got %d", len(appErr.Hints))
|
||||
}
|
||||
|
||||
// Test error message is non-empty
|
||||
if appErr.Error() == "" {
|
||||
t.Error("Expected non-empty error message")
|
||||
}
|
||||
|
||||
// Test Unwrap
|
||||
if !stderrors.Is(appErr, cause) {
|
||||
t.Error("Expected errors.Is to match the cause via Unwrap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionError(t *testing.T) {
|
||||
connErr := apperrors.NewConnectionError(
|
||||
"auth",
|
||||
"Authentication failed",
|
||||
"ssh: handshake failed",
|
||||
[]string{"Check credentials", "Verify key permissions"},
|
||||
)
|
||||
|
||||
if connErr.Type != "auth" {
|
||||
t.Errorf("Expected type 'auth', got '%s'", connErr.Type)
|
||||
}
|
||||
|
||||
if len(connErr.Hints) != 2 {
|
||||
t.Errorf("Expected 2 hints, got %d", len(connErr.Hints))
|
||||
}
|
||||
|
||||
if connErr.Error() == "" {
|
||||
t.Error("Expected non-empty error message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSSHError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputErr error
|
||||
expectedType string
|
||||
}{
|
||||
{
|
||||
name: "connection refused",
|
||||
inputErr: stderrors.New("dial tcp: connection refused"),
|
||||
expectedType: "network",
|
||||
},
|
||||
{
|
||||
name: "authentication failed",
|
||||
inputErr: stderrors.New("ssh: handshake failed: ssh: unable to authenticate"),
|
||||
expectedType: "auth",
|
||||
},
|
||||
{
|
||||
name: "timeout",
|
||||
inputErr: stderrors.New("dial tcp: connection timed out"),
|
||||
expectedType: "timeout",
|
||||
},
|
||||
{
|
||||
name: "no such host",
|
||||
inputErr: stderrors.New("dial tcp: lookup: no such host"),
|
||||
expectedType: "config",
|
||||
},
|
||||
{
|
||||
name: "permission denied",
|
||||
inputErr: stderrors.New("ssh: permission denied"),
|
||||
expectedType: "auth",
|
||||
},
|
||||
{
|
||||
name: "unknown error",
|
||||
inputErr: stderrors.New("something went wrong"),
|
||||
expectedType: "unknown",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
connErr := apperrors.HandleSSHError(tt.inputErr)
|
||||
if connErr == nil {
|
||||
t.Fatal("Expected non-nil ConnectionError")
|
||||
}
|
||||
if connErr.Type != tt.expectedType {
|
||||
t.Errorf("Expected type '%s', got '%s'", tt.expectedType, connErr.Type)
|
||||
}
|
||||
if len(connErr.Hints) == 0 {
|
||||
t.Error("Expected at least one hint")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSSHErrorNil(t *testing.T) {
|
||||
result := apperrors.HandleSSHError(nil)
|
||||
if result != nil {
|
||||
t.Error("Expected nil for nil input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatConnectionError(t *testing.T) {
|
||||
connErr := apperrors.NewConnectionError(
|
||||
"auth",
|
||||
"Authentication failed",
|
||||
"ssh: handshake failed",
|
||||
[]string{"Check credentials", "Verify key permissions"},
|
||||
)
|
||||
|
||||
output := apperrors.FormatConnectionError(connErr)
|
||||
|
||||
if output == "" {
|
||||
t.Error("Expected non-empty formatted output")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Authentication failed") {
|
||||
t.Error("Expected output to contain error message")
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "Possible solutions") {
|
||||
t.Error("Expected output to contain hints section")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user