// Package errors tests error handling functionality. package errors import ( "testing" ) func TestErrorCodeConstants(t *testing.T) { // Test that all error codes are defined tests := []struct { name string code ErrorCode expected string }{ {"GraphRecursionLimit", ErrorCodeGraphRecursionLimit, "GRAPH_RECURSION_LIMIT"}, {"InvalidConcurrentGraphUpdate", ErrorCodeInvalidConcurrentGraphUpdate, "INVALID_CONCURRENT_GRAPH_UPDATE"}, {"InvalidGraphNodeReturnValue", ErrorCodeInvalidGraphNodeReturnValue, "INVALID_GRAPH_NODE_RETURN_VALUE"}, {"MultipleSubgraphs", ErrorCodeMultipleSubgraphs, "MULTIPLE_SUBGRAPHS"}, {"InvalidChatHistory", ErrorCodeInvalidChatHistory, "INVALID_CHAT_HISTORY"}, {"CheckpointConflict", ErrorCodeCheckpointConflict, "CHECKPOINT_CONFLICT"}, {"InvalidState", ErrorCodeInvalidState, "INVALID_STATE"}, {"NodeNotFound", ErrorCodeNodeNotFound, "NODE_NOT_FOUND"}, {"ChannelNotFound", ErrorCodeChannelNotFound, "CHANNEL_NOT_FOUND"}, {"Timeout", ErrorCodeTimeout, "TIMEOUT"}, {"Cancellation", ErrorCodeCancellation, "CANCELLATION"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if string(tt.code) != tt.expected { t.Errorf("Expected %s, got %s", tt.expected, string(tt.code)) } }) } } func TestCreateErrorMessage(t *testing.T) { message := "Test error message" code := ErrorCodeGraphRecursionLimit result := CreateErrorMessage(message, code) expected := "Test error message\nFor troubleshooting, visit: https://ragflow/internal/harness/docs/errors/GRAPH_RECURSION_LIMIT" if result != expected { t.Errorf("Expected:\n%s\n\nGot:\n%s", expected, result) } } func TestNewErrorContext(t *testing.T) { err := NewErrorContext(ErrorCodeInvalidState, "Invalid state", nil) if err.ErrorCode != ErrorCodeInvalidState { t.Errorf("Expected ErrorCodeInvalidState, got %s", err.ErrorCode) } if err.Message != "Invalid state" { t.Errorf("Expected 'Invalid state', got '%s'", err.Message) } if err.Cause != nil { t.Error("Cause should be nil") } if len(err.StackTrace) == 0 { t.Error("StackTrace should not be empty") } } func TestErrorContext_Error(t *testing.T) { baseErr := &GraphBubbleUp{ Message: "Base error", } err := NewErrorContext(ErrorCodeInvalidState, "Invalid state", baseErr) errorStr := err.Error() // Check that error code is included if len(errorStr) == 0 { t.Error("Error string should not be empty") } // Check that message is included if !contains(errorStr, "Invalid state") { t.Error("Error string should contain 'Invalid state'") } // Check that cause is included if !contains(errorStr, "Base error") { t.Error("Error string should contain cause") } } func TestErrorContext_Metadata(t *testing.T) { err := NewErrorContext(ErrorCodeInvalidState, "Invalid state", nil) err.AddMetadata("key1", "value1") err.AddMetadata("key2", 42) val, ok := err.GetMetadata("key1") if !ok { t.Error("Expected key1 to exist") } if val != "value1" { t.Errorf("Expected 'value1', got '%v'", val) } val, ok = err.GetMetadata("key2") if !ok { t.Error("Expected key2 to exist") } if val != 42 { t.Errorf("Expected 42, got '%v'", val) } _, ok = err.GetMetadata("key3") if ok { t.Error("key3 should not exist") } } func TestWrapError(t *testing.T) { baseErr := &GraphBubbleUp{Message: "Base error"} wrapped := WrapError(baseErr, ErrorCodeInvalidState, "Invalid state") if wrapped == nil { t.Error("Wrapped error should not be nil") } // Wrap nil should return nil nilWrapped := WrapError(nil, ErrorCodeInvalidState, "Invalid state") if nilWrapped != nil { t.Error("Wrapping nil should return nil") } } func TestGetErrorCode(t *testing.T) { tests := []struct { name string err error expected ErrorCode }{ {"GraphRecursionError", &GraphRecursionError{Limit: 100}, ErrorCodeGraphRecursionLimit}, {"GraphInterrupt", &GraphInterrupt{Interrupts: []interface{}{"test"}}, ErrorCodeCancellation}, {"ParentCommand", &ParentCommand{Command: "test"}, ErrorCodeInvalidConcurrentGraphUpdate}, {"ErrorContext", NewErrorContext(ErrorCodeInvalidState, "test", nil), ErrorCodeInvalidState}, {"Nil", nil, ""}, {"GenericError", &GraphBubbleUp{Message: "test"}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := GetErrorCode(tt.err) if result != tt.expected { t.Errorf("Expected %s, got %s", tt.expected, result) } }) } } func TestGetErrorStack(t *testing.T) { err := NewErrorContext(ErrorCodeInvalidState, "test", nil) stack := GetErrorStack(err) if stack == nil { t.Error("Stack should not be nil for ErrorContext") } if len(stack) == 0 { t.Error("Stack should not be empty") } // Test with non-ErrorContext nilStack := GetErrorStack(&GraphBubbleUp{Message: "test"}) if nilStack != nil { t.Error("Stack should be nil for non-ErrorContext") } } func TestChainError(t *testing.T) { baseErr := &GraphBubbleUp{Message: "Base error"} newErr := &GraphBubbleUp{Message: "New error"} chained := ChainError(baseErr, newErr) if chained == nil { t.Error("Chained error should not be nil") } errorStr := chained.Error() if !contains(errorStr, "New error") { t.Error("Chained error should contain new error") } if !contains(errorStr, "Base error") { t.Error("Chained error should contain base error") } // Test with nil base nilChained := ChainError(nil, newErr) if nilChained != newErr { t.Error("Chaining nil base should return new error") } // Test with nil new nilChained2 := ChainError(baseErr, nil) if nilChained2 != baseErr { t.Error("Chaining nil new should return base error") } } func TestFormatError(t *testing.T) { baseErr := &GraphBubbleUp{Message: "Base error"} err := NewErrorContext(ErrorCodeInvalidState, "Invalid state", baseErr) formatted := FormatError(err) if len(formatted) == 0 { t.Error("Formatted error should not be empty") } if !contains(formatted, "Invalid state") { t.Error("Formatted error should contain message") } // Test with nil nilFormatted := FormatError(nil) if nilFormatted != "" { t.Error("Formatting nil should return empty string") } } func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && findSubstring(s, substr)) } func findSubstring(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }