583 lines
14 KiB
Go
583 lines
14 KiB
Go
package operation
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
gitea_errors "gitea.com/gitea/gitea-mcp/pkg/errors"
|
|
)
|
|
|
|
func TestErrorScenarios_NotFound(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
errMsg string
|
|
ctx map[string]string
|
|
isNotFound bool
|
|
}{
|
|
{
|
|
name: "404 file not found",
|
|
errMsg: "request failed with status 404: GetContents error",
|
|
ctx: map[string]string{"operation": "GetFile", "path": "README.md"},
|
|
isNotFound: true,
|
|
},
|
|
{
|
|
name: "404 repo not found",
|
|
errMsg: "request failed with status 404: GetRepo error",
|
|
ctx: map[string]string{"operation": "GetRepo"},
|
|
isNotFound: true,
|
|
},
|
|
{
|
|
name: "404 issue not found",
|
|
errMsg: "request failed with status 404: GetIssue error",
|
|
ctx: map[string]string{"operation": "GetIssue"},
|
|
isNotFound: true,
|
|
},
|
|
{
|
|
name: "404 pull request not found",
|
|
errMsg: "request failed with status 404: GetPullRequest error",
|
|
ctx: map[string]string{"operation": "GetPullRequest"},
|
|
isNotFound: true,
|
|
},
|
|
{
|
|
name: "401 auth error not found",
|
|
errMsg: "request failed with status 401: unauthorized",
|
|
ctx: map[string]string{"operation": "GetFile"},
|
|
isNotFound: false,
|
|
},
|
|
{
|
|
name: "500 server error not found",
|
|
errMsg: "request failed with status 500: internal server error",
|
|
ctx: map[string]string{"operation": "GetFile"},
|
|
isNotFound: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := errors.New(tt.errMsg)
|
|
translated := gitea_errors.TranslateError(err, tt.ctx)
|
|
|
|
if gitea_errors.IsNotFound(translated) != tt.isNotFound {
|
|
t.Errorf("IsNotFound() = %v, want %v", gitea_errors.IsNotFound(translated), tt.isNotFound)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_AuthErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
errMsg string
|
|
isAuthErr bool
|
|
}{
|
|
{
|
|
name: "401 unauthorized",
|
|
errMsg: "request failed with status 401: unauthorized",
|
|
isAuthErr: true,
|
|
},
|
|
{
|
|
name: "403 forbidden",
|
|
errMsg: "request failed with status 403: forbidden",
|
|
isAuthErr: true,
|
|
},
|
|
{
|
|
name: "authentication failed message",
|
|
errMsg: "authentication failed",
|
|
isAuthErr: true,
|
|
},
|
|
{
|
|
name: "permission denied message",
|
|
errMsg: "permission denied",
|
|
isAuthErr: true,
|
|
},
|
|
{
|
|
name: "token error message",
|
|
errMsg: "check your access token",
|
|
isAuthErr: true,
|
|
},
|
|
{
|
|
name: "404 not auth error",
|
|
errMsg: "request failed with status 404: not found",
|
|
isAuthErr: false,
|
|
},
|
|
{
|
|
name: "500 not auth error",
|
|
errMsg: "request failed with status 500: server error",
|
|
isAuthErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := errors.New(tt.errMsg)
|
|
translated := gitea_errors.TranslateError(err, nil)
|
|
|
|
if gitea_errors.IsAuthError(translated) != tt.isAuthErr {
|
|
t.Errorf("IsAuthError() = %v, want %v", gitea_errors.IsAuthError(translated), tt.isAuthErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_NetworkErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
errMsg string
|
|
isNetworkErr bool
|
|
isTimeout bool
|
|
}{
|
|
{
|
|
name: "connection refused",
|
|
errMsg: "connection refused",
|
|
isNetworkErr: true,
|
|
isTimeout: false,
|
|
},
|
|
{
|
|
name: "no such host",
|
|
errMsg: "no such host example.com",
|
|
isNetworkErr: true,
|
|
isTimeout: false,
|
|
},
|
|
{
|
|
name: "network unreachable",
|
|
errMsg: "network unreachable",
|
|
isNetworkErr: true,
|
|
isTimeout: false,
|
|
},
|
|
{
|
|
name: "timeout error",
|
|
errMsg: "request timeout",
|
|
isNetworkErr: true,
|
|
isTimeout: true,
|
|
},
|
|
{
|
|
name: "deadline exceeded",
|
|
errMsg: "context deadline exceeded",
|
|
isNetworkErr: true,
|
|
isTimeout: true,
|
|
},
|
|
{
|
|
name: "dial tcp",
|
|
errMsg: "dial tcp: connection refused",
|
|
isNetworkErr: true,
|
|
isTimeout: false,
|
|
},
|
|
{
|
|
name: "not network error",
|
|
errMsg: "file not found",
|
|
isNetworkErr: false,
|
|
isTimeout: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := errors.New(tt.errMsg)
|
|
translated := gitea_errors.TranslateError(err, nil)
|
|
|
|
if gitea_errors.IsNetworkError(translated) != tt.isNetworkErr {
|
|
t.Errorf("IsNetworkError() = %v, want %v", gitea_errors.IsNetworkError(translated), tt.isNetworkErr)
|
|
}
|
|
if gitea_errors.IsTimeout(translated) != tt.isTimeout {
|
|
t.Errorf("IsTimeout() = %v, want %v", gitea_errors.IsTimeout(translated), tt.isTimeout)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_ActionsAPIUnavailable(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
errMsg string
|
|
isActionsUnavailable bool
|
|
}{
|
|
{
|
|
name: "actions 404",
|
|
errMsg: "actions endpoint returned 404",
|
|
isActionsUnavailable: true,
|
|
},
|
|
{
|
|
name: "actions 405",
|
|
errMsg: "actions endpoint returned 405",
|
|
isActionsUnavailable: true,
|
|
},
|
|
{
|
|
name: "actions not found",
|
|
errMsg: "actions workflow not found",
|
|
isActionsUnavailable: true,
|
|
},
|
|
{
|
|
name: "actions method not allowed",
|
|
errMsg: "actions method not allowed",
|
|
isActionsUnavailable: true,
|
|
},
|
|
{
|
|
name: "actions enhanced error",
|
|
errMsg: "not supported on this Gitea version",
|
|
isActionsUnavailable: true,
|
|
},
|
|
{
|
|
name: "other actions error",
|
|
errMsg: "actions completed successfully",
|
|
isActionsUnavailable: false,
|
|
},
|
|
{
|
|
name: "file not found not actions",
|
|
errMsg: "file not found",
|
|
isActionsUnavailable: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := errors.New(tt.errMsg)
|
|
if gitea_errors.IsActionsAPIUnavailable(err) != tt.isActionsUnavailable {
|
|
t.Errorf("IsActionsAPIUnavailable() = %v, want %v", gitea_errors.IsActionsAPIUnavailable(err), tt.isActionsUnavailable)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_HTTPStatusCodes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
statusCode int
|
|
isUnauthorized bool
|
|
isForbidden bool
|
|
isNotFound bool
|
|
isServerError bool
|
|
}{
|
|
{
|
|
name: "HTTP 401",
|
|
statusCode: 401,
|
|
isUnauthorized: true,
|
|
isForbidden: false,
|
|
isNotFound: false,
|
|
isServerError: false,
|
|
},
|
|
{
|
|
name: "HTTP 403",
|
|
statusCode: 403,
|
|
isUnauthorized: false,
|
|
isForbidden: true,
|
|
isNotFound: false,
|
|
isServerError: false,
|
|
},
|
|
{
|
|
name: "HTTP 404",
|
|
statusCode: 404,
|
|
isUnauthorized: false,
|
|
isForbidden: false,
|
|
isNotFound: true,
|
|
isServerError: false,
|
|
},
|
|
{
|
|
name: "HTTP 500",
|
|
statusCode: 500,
|
|
isUnauthorized: false,
|
|
isForbidden: false,
|
|
isNotFound: false,
|
|
isServerError: true,
|
|
},
|
|
{
|
|
name: "HTTP 502",
|
|
statusCode: 502,
|
|
isUnauthorized: false,
|
|
isForbidden: false,
|
|
isNotFound: false,
|
|
isServerError: true,
|
|
},
|
|
{
|
|
name: "HTTP 503",
|
|
statusCode: 503,
|
|
isUnauthorized: false,
|
|
isForbidden: false,
|
|
isNotFound: false,
|
|
isServerError: true,
|
|
},
|
|
{
|
|
name: "HTTP 200",
|
|
statusCode: 200,
|
|
isUnauthorized: false,
|
|
isForbidden: false,
|
|
isNotFound: false,
|
|
isServerError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := &testHTTPError{status: tt.statusCode, message: "test error"}
|
|
|
|
if gitea_errors.IsUnauthorized(err) != tt.isUnauthorized {
|
|
t.Errorf("IsUnauthorized() = %v, want %v", gitea_errors.IsUnauthorized(err), tt.isUnauthorized)
|
|
}
|
|
if gitea_errors.IsForbidden(err) != tt.isForbidden {
|
|
t.Errorf("IsForbidden() = %v, want %v", gitea_errors.IsForbidden(err), tt.isForbidden)
|
|
}
|
|
if gitea_errors.IsNotFoundHTTP(err) != tt.isNotFound {
|
|
t.Errorf("IsNotFoundHTTP() = %v, want %v", gitea_errors.IsNotFoundHTTP(err), tt.isNotFound)
|
|
}
|
|
if gitea_errors.IsServerError(err) != tt.isServerError {
|
|
t.Errorf("IsServerError() = %v, want %v", gitea_errors.IsServerError(err), tt.isServerError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_EnhancedErrorChaining(t *testing.T) {
|
|
original := errors.New("original error")
|
|
|
|
err := gitea_errors.TranslateError(original, map[string]string{
|
|
"operation": "TestOp",
|
|
"param1": "value1",
|
|
})
|
|
|
|
if !errors.Is(err, original) {
|
|
t.Error("enhanced error should wrap original")
|
|
}
|
|
|
|
var enhanced *gitea_errors.EnhancedError
|
|
if !errors.As(err, &enhanced) {
|
|
t.Fatal("should be able to extract EnhancedError")
|
|
}
|
|
|
|
if enhanced.Operation != "TestOp" {
|
|
t.Errorf("operation = %q, want %q", enhanced.Operation, "TestOp")
|
|
}
|
|
|
|
if enhanced.Context["param1"] != "value1" {
|
|
t.Errorf("context[param1] = %q, want %q", enhanced.Context["param1"], "value1")
|
|
}
|
|
|
|
formatted := enhanced.Format()
|
|
if formatted == "" {
|
|
t.Error("Format() should return non-empty string")
|
|
}
|
|
|
|
detailed := enhanced.FormatDetailed()
|
|
if detailed == "" {
|
|
t.Error("FormatDetailed() should return non-empty string")
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_FluentAPI(t *testing.T) {
|
|
original := errors.New("test error")
|
|
enhanced := gitea_errors.TranslateError(original, nil).(*gitea_errors.EnhancedError)
|
|
|
|
result := enhanced.
|
|
WithOperation("GetFile").
|
|
WithParam("owner", "gitea").
|
|
WithParam("repo", "tea").
|
|
WithParam("path", "README.md")
|
|
|
|
if result != enhanced {
|
|
t.Error("fluent API should return 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")
|
|
}
|
|
if enhanced.Context["path"] != "README.md" {
|
|
t.Errorf("context[path] = %q, want %q", enhanced.Context["path"], "README.md")
|
|
}
|
|
}
|
|
|
|
type testHTTPError struct {
|
|
status int
|
|
message string
|
|
}
|
|
|
|
func (e *testHTTPError) Error() string { return e.message }
|
|
func (e *testHTTPError) Status() int { return e.status }
|
|
|
|
func TestErrorScenarios_CrossToolErrorConsistency(t *testing.T) {
|
|
ctx := map[string]string{
|
|
"owner": "test-owner",
|
|
"repo": "test-repo",
|
|
"operation": "CrossToolTest",
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
errMsg string
|
|
category gitea_errors.ErrorCategory
|
|
}{
|
|
{
|
|
name: "file operation error",
|
|
errMsg: "GetContents failed",
|
|
category: gitea_errors.CategoryFile,
|
|
},
|
|
{
|
|
name: "auth operation error",
|
|
errMsg: "GetUser failed",
|
|
category: gitea_errors.CategoryAuth,
|
|
},
|
|
{
|
|
name: "repo operation error",
|
|
errMsg: "GetRepo failed",
|
|
category: gitea_errors.CategoryRepo,
|
|
},
|
|
{
|
|
name: "issue operation error",
|
|
errMsg: "GetIssue failed",
|
|
category: gitea_errors.CategoryIssue,
|
|
},
|
|
{
|
|
name: "pull request operation error",
|
|
errMsg: "GetPullRequest failed",
|
|
category: gitea_errors.CategoryPull,
|
|
},
|
|
{
|
|
name: "branch operation error",
|
|
errMsg: "GetBranch failed",
|
|
category: gitea_errors.CategoryBranch,
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := errors.New(tt.errMsg)
|
|
translated := gitea_errors.TranslateError(err, ctx)
|
|
|
|
var enhanced *gitea_errors.EnhancedError
|
|
if !errors.As(translated, &enhanced) {
|
|
t.Fatal("expected EnhancedError")
|
|
}
|
|
|
|
if enhanced.Category != tt.category {
|
|
t.Errorf("category = %q, want %q", enhanced.Category, tt.category)
|
|
}
|
|
|
|
if enhanced.Context["owner"] != "test-owner" {
|
|
t.Errorf("context[owner] not preserved")
|
|
}
|
|
if enhanced.Context["repo"] != "test-repo" {
|
|
t.Errorf("context[repo] not preserved")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_NilHandling(t *testing.T) {
|
|
if gitea_errors.TranslateError(nil, nil) != nil {
|
|
t.Error("TranslateError(nil) should return nil")
|
|
}
|
|
|
|
if gitea_errors.IsNotFound(nil) {
|
|
t.Error("IsNotFound(nil) should return false")
|
|
}
|
|
|
|
if gitea_errors.IsAuthError(nil) {
|
|
t.Error("IsAuthError(nil) should return false")
|
|
}
|
|
|
|
if gitea_errors.IsNetworkError(nil) {
|
|
t.Error("IsNetworkError(nil) should return false")
|
|
}
|
|
|
|
if gitea_errors.IsTimeout(nil) {
|
|
t.Error("IsTimeout(nil) should return false")
|
|
}
|
|
|
|
if gitea_errors.IsActionsAPIUnavailable(nil) {
|
|
t.Error("IsActionsAPIUnavailable(nil) should return false")
|
|
}
|
|
|
|
if gitea_errors.IsServerError(nil) {
|
|
t.Error("IsServerError(nil) should return false")
|
|
}
|
|
|
|
if gitea_errors.Wrap(nil, "operation") != nil {
|
|
t.Error("Wrap(nil) should return nil")
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_ServerErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
errMsg string
|
|
isServerErr bool
|
|
}{
|
|
{
|
|
name: "500 internal server error",
|
|
errMsg: "status 500",
|
|
isServerErr: true,
|
|
},
|
|
{
|
|
name: "502 bad gateway",
|
|
errMsg: "status 502",
|
|
isServerErr: true,
|
|
},
|
|
{
|
|
name: "503 service unavailable",
|
|
errMsg: "status 503",
|
|
isServerErr: true,
|
|
},
|
|
{
|
|
name: "504 gateway timeout",
|
|
errMsg: "status 504",
|
|
isServerErr: true,
|
|
},
|
|
{
|
|
name: "404 not server error",
|
|
errMsg: "status 404",
|
|
isServerErr: false,
|
|
},
|
|
{
|
|
name: "200 not server error",
|
|
errMsg: "status 200",
|
|
isServerErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := errors.New(tt.errMsg)
|
|
if gitea_errors.IsServerError(err) != tt.isServerErr {
|
|
t.Errorf("IsServerError() = %v, want %v", gitea_errors.IsServerError(err), tt.isServerErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_ErrorWrapping(t *testing.T) {
|
|
original := errors.New("original error")
|
|
|
|
wrapped := gitea_errors.Wrap(original, "GetFile")
|
|
|
|
if !errors.Is(wrapped, original) {
|
|
t.Error("wrapped error should contain original")
|
|
}
|
|
|
|
var enhanced *gitea_errors.EnhancedError
|
|
if !errors.As(wrapped, &enhanced) {
|
|
t.Fatal("wrapped should be EnhancedError")
|
|
}
|
|
|
|
if enhanced.Operation != "GetFile" {
|
|
t.Errorf("operation = %q, want %q", enhanced.Operation, "GetFile")
|
|
}
|
|
}
|
|
|
|
func TestErrorScenarios_ErrorUnwrap(t *testing.T) {
|
|
original := errors.New("original error")
|
|
enhanced := gitea_errors.NewEnhancedError(original, "translated", gitea_errors.CategoryFile)
|
|
|
|
unwrapped := enhanced.Unwrap()
|
|
if unwrapped != original {
|
|
t.Error("Unwrap() should return original error")
|
|
}
|
|
|
|
if !errors.Is(enhanced, original) {
|
|
t.Error("errors.Is should find original through unwrapping")
|
|
}
|
|
}
|