mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-01-26 11:34:03 +00:00
* chore(ci): update go to 1.25, add permissions and envs * fix(ci): update pr-lint.yml * chore: update go, fix linting * fix: tests and linting * fix(lint): lint fixes, renovate should now pass * fix: updates, security upgrades * chore: workflow updates, lint * fix: more lint, checkmake, and other fixes * fix: more lint, convert scripts to POSIX compliant * fix: simplify codeql workflow * tests: increase test coverage, fix found issues * fix(lint): editorconfig checking, add to linters * fix(lint): shellcheck, add to linters * fix(lint): apply cr comment suggestions * fix(ci): remove step-security/harden-runner * fix(lint): remove duplication, apply cr fixes * fix(ci): tests in CI/CD pipeline * chore(lint): deduplication of strings * fix(lint): apply cr comment suggestions * fix(ci): actionlint * fix(lint): apply cr comment suggestions * chore: lint, add deps management
368 lines
9.1 KiB
Go
368 lines
9.1 KiB
Go
package gibidiutils
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestErrorTypeString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
errType ErrorType
|
|
expected string
|
|
}{
|
|
{
|
|
name: "CLI error type",
|
|
errType: ErrorTypeCLI,
|
|
expected: "CLI",
|
|
},
|
|
{
|
|
name: "FileSystem error type",
|
|
errType: ErrorTypeFileSystem,
|
|
expected: "FileSystem",
|
|
},
|
|
{
|
|
name: "Processing error type",
|
|
errType: ErrorTypeProcessing,
|
|
expected: "Processing",
|
|
},
|
|
{
|
|
name: "Configuration error type",
|
|
errType: ErrorTypeConfiguration,
|
|
expected: "Configuration",
|
|
},
|
|
{
|
|
name: "IO error type",
|
|
errType: ErrorTypeIO,
|
|
expected: "IO",
|
|
},
|
|
{
|
|
name: "Validation error type",
|
|
errType: ErrorTypeValidation,
|
|
expected: "Validation",
|
|
},
|
|
{
|
|
name: "Unknown error type",
|
|
errType: ErrorTypeUnknown,
|
|
expected: "Unknown",
|
|
},
|
|
{
|
|
name: "Invalid error type",
|
|
errType: ErrorType(999),
|
|
expected: "Unknown",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.errType.String()
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStructuredErrorMethods(t *testing.T) {
|
|
t.Run("Error method", func(t *testing.T) {
|
|
err := &StructuredError{
|
|
Type: ErrorTypeValidation,
|
|
Code: CodeValidationRequired,
|
|
Message: "field is required",
|
|
}
|
|
expected := "Validation [REQUIRED]: field is required"
|
|
assert.Equal(t, expected, err.Error())
|
|
})
|
|
|
|
t.Run("Error method with context", func(t *testing.T) {
|
|
err := &StructuredError{
|
|
Type: ErrorTypeFileSystem,
|
|
Code: CodeFSNotFound,
|
|
Message: testErrFileNotFound,
|
|
Context: map[string]interface{}{
|
|
"path": "/test/file.txt",
|
|
},
|
|
}
|
|
errStr := err.Error()
|
|
assert.Contains(t, errStr, "FileSystem")
|
|
assert.Contains(t, errStr, "NOT_FOUND")
|
|
assert.Contains(t, errStr, testErrFileNotFound)
|
|
assert.Contains(t, errStr, "/test/file.txt")
|
|
assert.Contains(t, errStr, "path")
|
|
})
|
|
|
|
t.Run("Unwrap method", func(t *testing.T) {
|
|
innerErr := errors.New("inner error")
|
|
err := &StructuredError{
|
|
Type: ErrorTypeIO,
|
|
Code: CodeIOFileWrite,
|
|
Message: testErrWriteFailed,
|
|
Cause: innerErr,
|
|
}
|
|
assert.Equal(t, innerErr, err.Unwrap())
|
|
})
|
|
|
|
t.Run("Unwrap with nil cause", func(t *testing.T) {
|
|
err := &StructuredError{
|
|
Type: ErrorTypeIO,
|
|
Code: CodeIOFileWrite,
|
|
Message: testErrWriteFailed,
|
|
}
|
|
assert.Nil(t, err.Unwrap())
|
|
})
|
|
}
|
|
|
|
func TestWithContextMethods(t *testing.T) {
|
|
t.Run("WithContext", func(t *testing.T) {
|
|
err := &StructuredError{
|
|
Type: ErrorTypeValidation,
|
|
Code: CodeValidationFormat,
|
|
Message: testErrInvalidFormat,
|
|
}
|
|
|
|
err = err.WithContext("format", "xml")
|
|
err = err.WithContext("expected", "json")
|
|
|
|
assert.NotNil(t, err.Context)
|
|
assert.Equal(t, "xml", err.Context["format"])
|
|
assert.Equal(t, "json", err.Context["expected"])
|
|
})
|
|
|
|
t.Run("WithFilePath", func(t *testing.T) {
|
|
err := &StructuredError{
|
|
Type: ErrorTypeFileSystem,
|
|
Code: CodeFSPermission,
|
|
Message: "permission denied",
|
|
}
|
|
|
|
err = err.WithFilePath("/etc/passwd")
|
|
|
|
assert.Equal(t, "/etc/passwd", err.FilePath)
|
|
})
|
|
|
|
t.Run("WithLine", func(t *testing.T) {
|
|
err := &StructuredError{
|
|
Type: ErrorTypeProcessing,
|
|
Code: CodeProcessingFileRead,
|
|
Message: "read error",
|
|
}
|
|
|
|
err = err.WithLine(42)
|
|
|
|
assert.Equal(t, 42, err.Line)
|
|
})
|
|
}
|
|
|
|
func TestNewStructuredError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
errType ErrorType
|
|
code string
|
|
message string
|
|
filePath string
|
|
context map[string]interface{}
|
|
}{
|
|
{
|
|
name: "basic error",
|
|
errType: ErrorTypeValidation,
|
|
code: CodeValidationRequired,
|
|
message: "field is required",
|
|
filePath: "",
|
|
context: nil,
|
|
},
|
|
{
|
|
name: "error with file path",
|
|
errType: ErrorTypeFileSystem,
|
|
code: CodeFSNotFound,
|
|
message: testErrFileNotFound,
|
|
filePath: "/test/missing.txt",
|
|
context: nil,
|
|
},
|
|
{
|
|
name: "error with context",
|
|
errType: ErrorTypeIO,
|
|
code: CodeIOFileWrite,
|
|
message: testErrWriteFailed,
|
|
context: map[string]interface{}{
|
|
"size": 1024,
|
|
"error": "disk full",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := NewStructuredError(tt.errType, tt.code, tt.message, tt.filePath, tt.context)
|
|
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, tt.errType, err.Type)
|
|
assert.Equal(t, tt.code, err.Code)
|
|
assert.Equal(t, tt.message, err.Message)
|
|
assert.Equal(t, tt.filePath, err.FilePath)
|
|
assert.Equal(t, tt.context, err.Context)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewStructuredErrorf(t *testing.T) {
|
|
err := NewStructuredErrorf(
|
|
ErrorTypeValidation,
|
|
CodeValidationSize,
|
|
"file size %d exceeds limit %d",
|
|
2048, 1024,
|
|
)
|
|
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, ErrorTypeValidation, err.Type)
|
|
assert.Equal(t, CodeValidationSize, err.Code)
|
|
assert.Equal(t, "file size 2048 exceeds limit 1024", err.Message)
|
|
}
|
|
|
|
func TestWrapError(t *testing.T) {
|
|
innerErr := errors.New("original error")
|
|
wrappedErr := WrapError(
|
|
innerErr,
|
|
ErrorTypeProcessing,
|
|
CodeProcessingFileRead,
|
|
"failed to process file",
|
|
)
|
|
|
|
assert.NotNil(t, wrappedErr)
|
|
assert.Equal(t, ErrorTypeProcessing, wrappedErr.Type)
|
|
assert.Equal(t, CodeProcessingFileRead, wrappedErr.Code)
|
|
assert.Equal(t, "failed to process file", wrappedErr.Message)
|
|
assert.Equal(t, innerErr, wrappedErr.Cause)
|
|
}
|
|
|
|
func TestWrapErrorf(t *testing.T) {
|
|
innerErr := errors.New("original error")
|
|
wrappedErr := WrapErrorf(
|
|
innerErr,
|
|
ErrorTypeIO,
|
|
CodeIOFileCreate,
|
|
"failed to create %s in %s",
|
|
"output.txt", "/tmp",
|
|
)
|
|
|
|
assert.NotNil(t, wrappedErr)
|
|
assert.Equal(t, ErrorTypeIO, wrappedErr.Type)
|
|
assert.Equal(t, CodeIOFileCreate, wrappedErr.Code)
|
|
assert.Equal(t, "failed to create output.txt in /tmp", wrappedErr.Message)
|
|
assert.Equal(t, innerErr, wrappedErr.Cause)
|
|
}
|
|
|
|
func TestSpecificErrorConstructors(t *testing.T) {
|
|
t.Run("NewMissingSourceError", func(t *testing.T) {
|
|
err := NewMissingSourceError()
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, ErrorTypeCLI, err.Type)
|
|
assert.Equal(t, CodeCLIMissingSource, err.Code)
|
|
assert.Contains(t, err.Message, "source")
|
|
})
|
|
|
|
t.Run("NewFileSystemError", func(t *testing.T) {
|
|
err := NewFileSystemError(CodeFSPermission, "access denied")
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, ErrorTypeFileSystem, err.Type)
|
|
assert.Equal(t, CodeFSPermission, err.Code)
|
|
assert.Equal(t, "access denied", err.Message)
|
|
})
|
|
|
|
t.Run("NewProcessingError", func(t *testing.T) {
|
|
err := NewProcessingError(CodeProcessingCollection, "collection failed")
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, ErrorTypeProcessing, err.Type)
|
|
assert.Equal(t, CodeProcessingCollection, err.Code)
|
|
assert.Equal(t, "collection failed", err.Message)
|
|
})
|
|
|
|
t.Run("NewIOError", func(t *testing.T) {
|
|
err := NewIOError(CodeIOFileWrite, testErrWriteFailed)
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, ErrorTypeIO, err.Type)
|
|
assert.Equal(t, CodeIOFileWrite, err.Code)
|
|
assert.Equal(t, testErrWriteFailed, err.Message)
|
|
})
|
|
|
|
t.Run("NewValidationError", func(t *testing.T) {
|
|
err := NewValidationError(CodeValidationFormat, testErrInvalidFormat)
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, ErrorTypeValidation, err.Type)
|
|
assert.Equal(t, CodeValidationFormat, err.Code)
|
|
assert.Equal(t, testErrInvalidFormat, err.Message)
|
|
})
|
|
}
|
|
|
|
// TestLogErrorf is already covered in errors_test.go
|
|
|
|
func TestStructuredErrorChaining(t *testing.T) {
|
|
// Test method chaining
|
|
err := NewStructuredError(
|
|
ErrorTypeFileSystem,
|
|
CodeFSNotFound,
|
|
testErrFileNotFound,
|
|
"",
|
|
nil,
|
|
).WithFilePath("/test.txt").WithLine(10).WithContext("operation", "read")
|
|
|
|
assert.Equal(t, "/test.txt", err.FilePath)
|
|
assert.Equal(t, 10, err.Line)
|
|
assert.Equal(t, "read", err.Context["operation"])
|
|
}
|
|
|
|
func TestErrorCodes(t *testing.T) {
|
|
// Test that all error codes are defined
|
|
codes := []string{
|
|
CodeCLIMissingSource,
|
|
CodeCLIInvalidArgs,
|
|
CodeFSPathResolution,
|
|
CodeFSPermission,
|
|
CodeFSNotFound,
|
|
CodeFSAccess,
|
|
CodeProcessingFileRead,
|
|
CodeProcessingCollection,
|
|
CodeProcessingTraversal,
|
|
CodeProcessingEncode,
|
|
CodeConfigValidation,
|
|
CodeConfigMissing,
|
|
CodeIOFileCreate,
|
|
CodeIOFileWrite,
|
|
CodeIOEncoding,
|
|
CodeIOWrite,
|
|
CodeIOFileRead,
|
|
CodeIOClose,
|
|
CodeValidationRequired,
|
|
CodeValidationFormat,
|
|
CodeValidationSize,
|
|
CodeValidationPath,
|
|
CodeResourceLimitFiles,
|
|
CodeResourceLimitTotalSize,
|
|
CodeResourceLimitMemory,
|
|
CodeResourceLimitTimeout,
|
|
}
|
|
|
|
// All codes should be non-empty strings
|
|
for _, code := range codes {
|
|
assert.NotEmpty(t, code, "Error code should not be empty")
|
|
assert.NotEqual(t, "", code, "Error code should be defined")
|
|
}
|
|
}
|
|
|
|
func TestErrorUnwrapChain(t *testing.T) {
|
|
// Test unwrapping through multiple levels
|
|
innermost := errors.New("innermost error")
|
|
middle := WrapError(innermost, ErrorTypeIO, CodeIOFileRead, "read failed")
|
|
outer := WrapError(middle, ErrorTypeProcessing, CodeProcessingFileRead, "processing failed")
|
|
|
|
// Test unwrapping
|
|
assert.Equal(t, middle, outer.Unwrap())
|
|
assert.Equal(t, innermost, middle.Unwrap())
|
|
|
|
// innermost is a plain error, doesn't have Unwrap() method
|
|
// No need to test it
|
|
|
|
// Test error chain messages
|
|
assert.Contains(t, outer.Error(), "Processing")
|
|
assert.Contains(t, middle.Error(), "IO")
|
|
}
|