Files

1171 lines
30 KiB
Go

package errors
import (
"errors"
"fmt"
"strings"
"testing"
"time"
)
func TestEnhancedError_Error(t *testing.T) {
tests := []struct {
name string
err *EnhancedError
expected string
}{
{
name: "with translated message",
err: &EnhancedError{
Translated: "File not found",
Original: errors.New("original error"),
},
expected: "File not found",
},
{
name: "without translated message uses original",
err: &EnhancedError{
Original: errors.New("original error"),
},
expected: "original error",
},
{
name: "empty error",
err: &EnhancedError{},
expected: "unknown error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.err.Error()
if got != tt.expected {
t.Errorf("Error() = %q, want %q", got, tt.expected)
}
})
}
}
func TestEnhancedError_Unwrap(t *testing.T) {
original := errors.New("original error")
enhanced := &EnhancedError{
Original: original,
Translated: "translated error",
}
unwrapped := enhanced.Unwrap()
if unwrapped != original {
t.Errorf("Unwrap() = %v, want %v", unwrapped, original)
}
if !errors.Is(enhanced, original) {
t.Error("errors.Is should find the original error")
}
}
func TestEnhancedError_WithContext(t *testing.T) {
err := &EnhancedError{
Translated: "File not found",
Context: make(map[string]string),
}
err.WithContext("owner", "gitea")
err.WithContext("repo", "tea")
if err.Context["owner"] != "gitea" {
t.Errorf("Context['owner'] = %q, want %q", err.Context["owner"], "gitea")
}
if err.Context["repo"] != "tea" {
t.Errorf("Context['repo'] = %q, want %q", err.Context["repo"], "tea")
}
}
func TestEnhancedError_Format(t *testing.T) {
err := &EnhancedError{
Original: errors.New("original error"),
Translated: "File not found",
Category: CategoryFile,
Operation: "GetFile",
Context: map[string]string{
"owner": "gitea",
"path": "README.md",
},
}
formatted := err.Format()
if !strings.Contains(formatted, "Operation: GetFile") {
t.Error("Format() should include operation")
}
if !strings.Contains(formatted, "Error: File not found") {
t.Error("Format() should include error message")
}
if !strings.Contains(formatted, "Category: file") {
t.Error("Format() should include category")
}
if !strings.Contains(formatted, "owner=gitea") {
t.Error("Format() should include context")
}
if !strings.Contains(formatted, "Original: original error") {
t.Error("Format() should include original error")
}
}
func TestTranslateError_Nil(t *testing.T) {
result := TranslateError(nil, nil)
if result != nil {
t.Error("TranslateError(nil) should return nil")
}
}
func TestTranslateError_AlreadyEnhanced(t *testing.T) {
original := errors.New("original")
enhanced := TranslateError(original, map[string]string{"operation": "GetFile"})
reEnhanced := TranslateError(enhanced, map[string]string{"owner": "gitea", "path": "README.md"})
err, ok := reEnhanced.(*EnhancedError)
if !ok {
t.Fatal("Expected *EnhancedError")
}
if err.Operation != "GetFile" {
t.Errorf("Operation = %q, want %q", err.Operation, "GetFile")
}
if err.Context["owner"] != "gitea" {
t.Errorf("Context['owner'] = %q, want %q", err.Context["owner"], "gitea")
}
if err.Context["path"] != "README.md" {
t.Errorf("Context['path'] = %q, want %q", err.Context["path"], "README.md")
}
}
func TestTranslateError_Mappings(t *testing.T) {
tests := []struct {
name string
input string
wantTranslated string
wantCategory ErrorCategory
}{
{
name: "HTTP 404 with GetContents",
input: "request failed with status 404: GetContents error",
wantTranslated: "File or directory not found",
wantCategory: CategoryFile,
},
{
name: "HTTP 404 with ListContents",
input: "request failed with status 404: ListContents error",
wantTranslated: "Directory not found or empty",
wantCategory: CategoryFile,
},
{
name: "HTTP 404 with GetUserByName",
input: "request failed with status 404: GetUserByName error",
wantTranslated: "User or organization not found",
wantCategory: CategoryAuth,
},
{
name: "HTTP 404 with GetRepo",
input: "request failed with status 404: GetRepo error",
wantTranslated: "Repository not found",
wantCategory: CategoryRepo,
},
{
name: "HTTP 404 with GetIssue",
input: "request failed with status 404: GetIssue error",
wantTranslated: "Issue not found",
wantCategory: CategoryIssue,
},
{
name: "HTTP 404 with GetPullRequest",
input: "request failed with status 404: GetPullRequest error",
wantTranslated: "Pull request not found",
wantCategory: CategoryPull,
},
{
name: "HTTP 404 with GetBranch",
input: "request failed with status 404: GetBranch error",
wantTranslated: "Branch or tag not found",
wantCategory: CategoryBranch,
},
{
name: "HTTP 404 generic",
input: "request failed with status 404",
wantTranslated: "Resource not found",
wantCategory: CategoryUnknown,
},
{
name: "HTTP 401",
input: "request failed with status 401: unauthorized",
wantTranslated: "Authentication failed - check your access token",
wantCategory: CategoryAuth,
},
{
name: "HTTP 403",
input: "request failed with status 403: forbidden",
wantTranslated: "Permission denied - you don't have access to this resource",
wantCategory: CategoryAuth,
},
{
name: "GetContents",
input: "GetContents failed",
wantTranslated: "File or directory not found",
wantCategory: CategoryFile,
},
{
name: "GetContentsOrList",
input: "GetContentsOrList failed",
wantTranslated: "File or directory not found",
wantCategory: CategoryFile,
},
{
name: "GetUserByName",
input: "GetUserByName failed",
wantTranslated: "User or organization not found",
wantCategory: CategoryAuth,
},
{
name: "GetUser",
input: "GetUser failed",
wantTranslated: "User or organization not found",
wantCategory: CategoryAuth,
},
{
name: "GetRepo",
input: "GetRepo failed",
wantTranslated: "Repository not found",
wantCategory: CategoryRepo,
},
{
name: "GetIssue",
input: "GetIssue failed",
wantTranslated: "Issue not found",
wantCategory: CategoryIssue,
},
{
name: "GetPullRequest",
input: "GetPullRequest failed",
wantTranslated: "Pull request not found",
wantCategory: CategoryPull,
},
{
name: "GetBranch",
input: "GetBranch failed",
wantTranslated: "Branch not found",
wantCategory: CategoryBranch,
},
{
name: "GetTag",
input: "GetTag failed",
wantTranslated: "Tag not found",
wantCategory: CategoryBranch,
},
{
name: "CreateFile",
input: "CreateFile failed",
wantTranslated: "Failed to create file - it may already exist",
wantCategory: CategoryFile,
},
{
name: "UpdateFile",
input: "UpdateFile failed",
wantTranslated: "Failed to update file - it may not exist or SHA mismatch",
wantCategory: CategoryFile,
},
{
name: "DeleteFile",
input: "DeleteFile failed",
wantTranslated: "Failed to delete file - it may not exist",
wantCategory: CategoryFile,
},
{
name: "CreateBranch",
input: "CreateBranch failed",
wantTranslated: "Failed to create branch",
wantCategory: CategoryBranch,
},
{
name: "DeleteBranch",
input: "DeleteBranch failed",
wantTranslated: "Failed to delete branch - it may not exist or be protected",
wantCategory: CategoryBranch,
},
{
name: "CreateIssue",
input: "CreateIssue failed",
wantTranslated: "Failed to create issue",
wantCategory: CategoryIssue,
},
{
name: "EditIssue",
input: "EditIssue failed",
wantTranslated: "Failed to update issue - it may not exist",
wantCategory: CategoryIssue,
},
{
name: "CreatePullRequest",
input: "CreatePullRequest failed",
wantTranslated: "Failed to create pull request",
wantCategory: CategoryPull,
},
{
name: "EditPullRequest",
input: "EditPullRequest failed",
wantTranslated: "Failed to update pull request",
wantCategory: CategoryPull,
},
{
name: "CreateRelease",
input: "CreateRelease failed",
wantTranslated: "Failed to create release",
wantCategory: CategoryRepo,
},
{
name: "EditRelease",
input: "EditRelease failed",
wantTranslated: "Failed to update release",
wantCategory: CategoryRepo,
},
{
name: "CreateWikiPage",
input: "CreateWikiPage failed",
wantTranslated: "Failed to create wiki page",
wantCategory: CategoryRepo,
},
{
name: "EditWikiPage",
input: "EditWikiPage failed",
wantTranslated: "Failed to update wiki page",
wantCategory: CategoryRepo,
},
{
name: "AddCollaborator",
input: "AddCollaborator failed",
wantTranslated: "Failed to add collaborator",
wantCategory: CategoryAuth,
},
{
name: "RemoveCollaborator",
input: "RemoveCollaborator failed",
wantTranslated: "Failed to remove collaborator",
wantCategory: CategoryAuth,
},
{
name: "CreateDeployKey",
input: "CreateDeployKey failed",
wantTranslated: "Failed to create deploy key",
wantCategory: CategoryAuth,
},
{
name: "DeleteDeployKey",
input: "DeleteDeployKey failed",
wantTranslated: "Failed to delete deploy key",
wantCategory: CategoryAuth,
},
{
name: "Timeout",
input: "request timeout",
wantTranslated: "Request timed out - the server took too long to respond",
wantCategory: CategoryNetwork,
},
{
name: "Deadline exceeded",
input: "context deadline exceeded",
wantTranslated: "Request timed out - the server took too long to respond",
wantCategory: CategoryNetwork,
},
{
name: "Connection refused",
input: "connection refused",
wantTranslated: "Network error - cannot connect to server",
wantCategory: CategoryNetwork,
},
{
name: "No such host",
input: "no such host example.com",
wantTranslated: "Network error - cannot connect to server",
wantCategory: CategoryNetwork,
},
{
name: "Unknown error",
input: "some random error message",
wantTranslated: "some random error message",
wantCategory: CategoryUnknown,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := errors.New(tt.input)
result := TranslateError(err, nil)
enhanced, ok := result.(*EnhancedError)
if !ok {
t.Fatal("Expected *EnhancedError")
}
if enhanced.Translated != tt.wantTranslated {
t.Errorf("Translated = %q, want %q", enhanced.Translated, tt.wantTranslated)
}
if enhanced.Category != tt.wantCategory {
t.Errorf("Category = %q, want %q", enhanced.Category, tt.wantCategory)
}
})
}
}
func TestIsNotFound(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "EnhancedError with CategoryFile",
err: &EnhancedError{Category: CategoryFile, Translated: "File not found"},
expected: true,
},
{
name: "EnhancedError with CategoryRepo",
err: &EnhancedError{Category: CategoryRepo, Translated: "Repo not found"},
expected: true,
},
{
name: "EnhancedError with CategoryIssue",
err: &EnhancedError{Category: CategoryIssue, Translated: "Issue not found"},
expected: true,
},
{
name: "EnhancedError with CategoryPull",
err: &EnhancedError{Category: CategoryPull, Translated: "PR not found"},
expected: true,
},
{
name: "EnhancedError with CategoryBranch",
err: &EnhancedError{Category: CategoryBranch, Translated: "Branch not found"},
expected: true,
},
{
name: "EnhancedError with CategoryAuth",
err: &EnhancedError{Category: CategoryAuth, Translated: "Auth failed"},
expected: false,
},
{
name: "EnhancedError with 'not found' in message",
err: &EnhancedError{Category: CategoryUnknown, Translated: "Something not found"},
expected: true,
},
{
name: "HTTP 404 error",
err: errors.New("request failed with status 404"),
expected: true,
},
{
name: "Simple not found message",
err: errors.New("file not found"),
expected: true,
},
{
name: "Other error",
err: errors.New("some other error"),
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsNotFound(tt.err)
if got != tt.expected {
t.Errorf("IsNotFound() = %v, want %v", got, tt.expected)
}
})
}
}
func TestIsAuthError(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "EnhancedError with CategoryAuth",
err: &EnhancedError{Category: CategoryAuth, Translated: "Auth failed"},
expected: true,
},
{
name: "HTTP 401 error",
err: errors.New("request failed with status 401"),
expected: true,
},
{
name: "HTTP 403 error",
err: errors.New("request failed with status 403"),
expected: true,
},
{
name: "Authentication in message",
err: errors.New("authentication failed"),
expected: true,
},
{
name: "Permission denied message",
err: errors.New("permission denied"),
expected: true,
},
{
name: "Access token message",
err: errors.New("check your access token"),
expected: true,
},
{
name: "File not found error",
err: errors.New("file not found"),
expected: false,
},
{
name: "EnhancedError with other category",
err: &EnhancedError{Category: CategoryFile, Translated: "File not found"},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsAuthError(tt.err)
if got != tt.expected {
t.Errorf("IsAuthError() = %v, want %v", got, tt.expected)
}
})
}
}
func TestIsActionsAPIUnavailable(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "EnhancedError with CategoryActions",
err: &EnhancedError{Category: CategoryActions, Translated: "Actions not available"},
expected: true,
},
{
name: "EnhancedError with not supported message",
err: &EnhancedError{Translated: "not supported on this Gitea version"},
expected: true,
},
{
name: "Actions with 404",
err: errors.New("actions endpoint returned 404"),
expected: true,
},
{
name: "Actions with not found",
err: errors.New("actions workflow not found"),
expected: true,
},
{
name: "Actions with method not allowed",
err: errors.New("actions method not allowed"),
expected: true,
},
{
name: "Actions with 405",
err: errors.New("actions endpoint returned 405"),
expected: true,
},
{
name: "Other actions error",
err: errors.New("actions completed successfully"),
expected: false,
},
{
name: "Non-actions error",
err: errors.New("file not found"),
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsActionsAPIUnavailable(tt.err)
if got != tt.expected {
t.Errorf("IsActionsAPIUnavailable() = %v, want %v", got, tt.expected)
}
})
}
}
func TestIsTimeout(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "EnhancedError with CategoryNetwork",
err: &EnhancedError{Category: CategoryNetwork, Translated: "Network error"},
expected: true,
},
{
name: "EnhancedError with timed out message",
err: &EnhancedError{Translated: "Request timed out"},
expected: true,
},
{
name: "Timeout in message",
err: errors.New("request timeout"),
expected: true,
},
{
name: "Deadline exceeded",
err: errors.New("context deadline exceeded"),
expected: true,
},
{
name: "Other error",
err: errors.New("file not found"),
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsTimeout(tt.err)
if got != tt.expected {
t.Errorf("IsTimeout() = %v, want %v", got, tt.expected)
}
})
}
}
func TestIsNetworkError(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "EnhancedError with CategoryNetwork",
err: &EnhancedError{Category: CategoryNetwork},
expected: true,
},
{
name: "Connection error",
err: errors.New("connection refused"),
expected: true,
},
{
name: "Network error",
err: errors.New("network unreachable"),
expected: true,
},
{
name: "No such host",
err: errors.New("no such host"),
expected: true,
},
{
name: "Dial TCP",
err: errors.New("dial tcp: connection refused"),
expected: true,
},
{
name: "Other error",
err: errors.New("file not found"),
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsNetworkError(tt.err)
if got != tt.expected {
t.Errorf("IsNetworkError() = %v, want %v", got, tt.expected)
}
})
}
}
func TestNewEnhancedError(t *testing.T) {
original := errors.New("original")
err := NewEnhancedError(original, "translated", CategoryFile)
if err.Original != original {
t.Error("Original should be set correctly")
}
if err.Translated != "translated" {
t.Errorf("Translated = %q, want %q", err.Translated, "translated")
}
if err.Category != CategoryFile {
t.Errorf("Category = %q, want %q", err.Category, CategoryFile)
}
if err.Context == nil {
t.Error("Context should be initialized")
}
}
func TestWrap(t *testing.T) {
t.Run("nil error returns nil", func(t *testing.T) {
result := Wrap(nil, "GetFile")
if result != nil {
t.Error("Wrap(nil) should return nil")
}
})
t.Run("wraps error with operation", func(t *testing.T) {
original := errors.New("original error")
result := Wrap(original, "GetFile")
enhanced, ok := result.(*EnhancedError)
if !ok {
t.Fatal("Expected *EnhancedError")
}
if enhanced.Operation != "GetFile" {
t.Errorf("Operation = %q, want %q", enhanced.Operation, "GetFile")
}
if !errors.Is(enhanced, original) {
t.Error("Original error should be preserved")
}
})
}
func TestIsHTTPError(t *testing.T) {
tests := []struct {
name string
err error
statusCode int
expected bool
}{
{
name: "nil error",
err: nil,
statusCode: 404,
expected: false,
},
{
name: "HTTPError with matching status",
err: &testHTTPError{status: 404, message: "not found"},
statusCode: 404,
expected: true,
},
{
name: "HTTPError with non-matching status",
err: &testHTTPError{status: 500, message: "server error"},
statusCode: 404,
expected: false,
},
{
name: "Error message with status code",
err: errors.New("request failed with status 404"),
statusCode: 404,
expected: true,
},
{
name: "Error message without status code",
err: errors.New("something else"),
statusCode: 404,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsHTTPError(tt.err, tt.statusCode)
if got != tt.expected {
t.Errorf("IsHTTPError() = %v, want %v", got, tt.expected)
}
})
}
}
func TestHTTPStatusHelpers(t *testing.T) {
t.Run("IsUnauthorized", func(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{"HTTP 401", &testHTTPError{status: 401}, true},
{"HTTP 403", &testHTTPError{status: 403}, false},
{"Error message with 401", errors.New("status 401"), true},
{"Other error", errors.New("other"), false},
{"nil", nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsUnauthorized(tt.err)
if got != tt.expected {
t.Errorf("IsUnauthorized() = %v, want %v", got, tt.expected)
}
})
}
})
t.Run("IsForbidden", func(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{"HTTP 403", &testHTTPError{status: 403}, true},
{"HTTP 401", &testHTTPError{status: 401}, false},
{"Error message with 403", errors.New("status 403"), true},
{"Other error", errors.New("other"), false},
{"nil", nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsForbidden(tt.err)
if got != tt.expected {
t.Errorf("IsForbidden() = %v, want %v", got, tt.expected)
}
})
}
})
t.Run("IsNotFoundHTTP", func(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{"HTTP 404", &testHTTPError{status: 404}, true},
{"HTTP 403", &testHTTPError{status: 403}, false},
{"Error message with 404", errors.New("status 404"), true},
{"Other error", errors.New("other"), false},
{"nil", nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsNotFoundHTTP(tt.err)
if got != tt.expected {
t.Errorf("IsNotFoundHTTP() = %v, want %v", got, tt.expected)
}
})
}
})
t.Run("IsServerError", func(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{"HTTP 500", &testHTTPError{status: 500}, true},
{"HTTP 502", &testHTTPError{status: 502}, true},
{"HTTP 503", &testHTTPError{status: 503}, true},
{"HTTP 404", &testHTTPError{status: 404}, false},
{"Error message with 500", errors.New("status 500"), true},
{"Error message with 502", errors.New("status 502"), true},
{"Other error", errors.New("other"), false},
{"nil", nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsServerError(tt.err)
if got != tt.expected {
t.Errorf("IsServerError() = %v, want %v", got, tt.expected)
}
})
}
})
}
func TestHTTPErrorInterface(t *testing.T) {
err := &testHTTPError{status: 404, message: "not found"}
var httpErr HTTPError
if !errors.As(err, &httpErr) {
t.Error("testHTTPError should implement HTTPError")
}
if httpErr.Status() != 404 {
t.Errorf("Status() = %d, want 404", httpErr.Status())
}
if httpErr.Error() != "not found" {
t.Errorf("Error() = %q, want %q", httpErr.Error(), "not found")
}
}
type testHTTPError struct {
status int
message string
}
func (e *testHTTPError) Error() string { return e.message }
func (e *testHTTPError) Status() int { return e.status }
func BenchmarkTranslateError(b *testing.B) {
err := errors.New("GetContents failed")
ctx := map[string]string{
"operation": "GetFile",
"owner": "gitea",
"repo": "tea",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = TranslateError(err, ctx)
}
}
func BenchmarkIsNotFound(b *testing.B) {
err := TranslateError(errors.New("GetContents failed"), nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = IsNotFound(err)
}
}
// ExampleTranslateError demonstrates how to use TranslateError.
func ExampleTranslateError() {
sdkErr := errors.New("GetContents failed with status 404")
err := TranslateError(sdkErr, map[string]string{
"operation": "GetFile",
"owner": "gitea",
"repo": "tea",
"path": "README.md",
})
fmt.Println(err.Error())
}
// ExampleEnhancedError_Format demonstrates the Format method.
func ExampleEnhancedError_Format() {
err := &EnhancedError{
Original: errors.New("GetContents failed"),
Translated: "File or directory not found",
Category: CategoryFile,
Operation: "GetFile",
Context: map[string]string{
"owner": "gitea",
"path": "README.md",
},
Timestamp: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
}
formatted := err.Format()
_ = formatted // Use the formatted string
}
// TestWithOperation tests the fluent API WithOperation method.
func TestWithOperation(t *testing.T) {
original := errors.New("original error")
enhanced := TranslateError(original, nil).(*EnhancedError)
result := enhanced.WithOperation("GetFile")
// Should return the same error for chaining
if result != enhanced {
t.Error("WithOperation should return the same error for chaining")
}
if enhanced.Operation != "GetFile" {
t.Errorf("Operation = %q, want %q", enhanced.Operation, "GetFile")
}
}
// TestWithParam tests the fluent API WithParam method.
func TestWithParam(t *testing.T) {
original := errors.New("original error")
enhanced := TranslateError(original, nil).(*EnhancedError)
result := enhanced.
WithOperation("GetFile").
WithParam("owner", "gitea").
WithParam("repo", "tea")
// Should return the same error for chaining
if result != enhanced {
t.Error("WithParam should return the same error for chaining")
}
if enhanced.Operation != "GetFile" {
t.Errorf("Operation = %q, want %q", enhanced.Operation, "GetFile")
}
if enhanced.Context["owner"] != "gitea" {
t.Errorf("Context['owner'] = %q, want %q", enhanced.Context["owner"], "gitea")
}
if enhanced.Context["repo"] != "tea" {
t.Errorf("Context['repo'] = %q, want %q", enhanced.Context["repo"], "tea")
}
}
// TestFormatDetailed tests the JSON-like structured error output.
func TestFormatDetailed(t *testing.T) {
err := &EnhancedError{
Original: errors.New("GetContents failed with status 404"),
Translated: "File or directory not found",
Category: CategoryFile,
Operation: "GetFile",
Context: map[string]string{
"owner": "gitea",
"path": "README.md",
},
Timestamp: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
}
detailed := err.FormatDetailed()
// Check that JSON output contains expected fields
if !strings.Contains(detailed, `"error"`) {
t.Error("FormatDetailed should include 'error' field")
}
if !strings.Contains(detailed, `"category"`) {
t.Error("FormatDetailed should include 'category' field")
}
if !strings.Contains(detailed, `"operation"`) {
t.Error("FormatDetailed should include 'operation' field")
}
if !strings.Contains(detailed, `"timestamp"`) {
t.Error("FormatDetailed should include 'timestamp' field")
}
if !strings.Contains(detailed, `"context"`) {
t.Error("FormatDetailed should include 'context' field")
}
if !strings.Contains(detailed, `"original"`) {
t.Error("FormatDetailed should include 'original' field")
}
// Check that values are included
if !strings.Contains(detailed, "File or directory not found") {
t.Error("FormatDetailed should include the error message")
}
if !strings.Contains(detailed, "gitea") {
t.Error("FormatDetailed should include context values")
}
if !strings.Contains(detailed, "2024-01-15T10:30:00Z") {
t.Error("FormatDetailed should include formatted timestamp")
}
}
// TestFormatDetailedWithoutOptionalFields tests JSON output with minimal fields.
func TestFormatDetailedWithoutOptionalFields(t *testing.T) {
err := &EnhancedError{
Original: errors.New("some error"),
Translated: "translated message",
Category: CategoryUnknown,
Timestamp: time.Now(),
}
detailed := err.FormatDetailed()
// Should not include operation field when empty
if strings.Contains(detailed, `"operation"`) {
t.Error("FormatDetailed should not include empty operation field")
}
// Should not include context field when empty
if strings.Contains(detailed, `"context"`) {
t.Error("FormatDetailed should not include empty context field")
}
// Should not include original when same as translated
if strings.Contains(detailed, `"original"`) {
t.Error("FormatDetailed should not include original when same as error")
}
}
// TestTimestampIsSet tests that timestamp is automatically set.
func TestTimestampIsSet(t *testing.T) {
before := time.Now().UTC()
err := TranslateError(errors.New("test error"), nil).(*EnhancedError)
after := time.Now().UTC()
if err.Timestamp.IsZero() {
t.Error("Timestamp should be set")
}
if err.Timestamp.Before(before) || err.Timestamp.After(after) {
t.Errorf("Timestamp %v should be between %v and %v", err.Timestamp, before, after)
}
}
// TestNewEnhancedErrorSetsTimestamp tests that NewEnhancedError sets timestamp.
func TestNewEnhancedErrorSetsTimestamp(t *testing.T) {
before := time.Now().UTC()
err := NewEnhancedError(errors.New("test"), "translated", CategoryFile)
after := time.Now().UTC()
if err.Timestamp.IsZero() {
t.Error("NewEnhancedError should set timestamp")
}
if err.Timestamp.Before(before) || err.Timestamp.After(after) {
t.Errorf("Timestamp %v should be between %v and %v", err.Timestamp, before, after)
}
}
// TestFluentAPIChaining tests complete fluent API usage.
func TestFluentAPIChaining(t *testing.T) {
original := errors.New("GetContents failed with status 404")
err := TranslateError(original, nil).
(*EnhancedError).
WithOperation("GetFile").
WithParam("owner", "gitea").
WithParam("repo", "tea").
WithParam("path", "README.md")
if err.Operation != "GetFile" {
t.Errorf("Operation = %q, want %q", err.Operation, "GetFile")
}
if err.Context["owner"] != "gitea" {
t.Errorf("Context['owner'] = %q, want %q", err.Context["owner"], "gitea")
}
if err.Context["repo"] != "tea" {
t.Errorf("Context['repo'] = %q, want %q", err.Context["repo"], "tea")
}
if err.Context["path"] != "README.md" {
t.Errorf("Context['path'] = %q, want %q", err.Context["path"], "README.md")
}
// Verify FormatDetailed works with fluent API built error
detailed := err.FormatDetailed()
if !strings.Contains(detailed, "GetFile") {
t.Error("FormatDetailed should include operation from fluent API")
}
}