Files
gh-action-readme/internal/errors/suggestions_test.go
Ismo Vuorinen f9823eef3e feat: add interactive wizard, contextual errors, and code improvements
- Add interactive configuration wizard with auto-detection and multi-format export
- Implement contextual error system with 14 error codes and actionable suggestions
- Add centralized progress indicators with consistent theming
- Fix all cyclomatic complexity issues (8 functions refactored)
- Eliminate code duplication with centralized utilities and error handling
- Add comprehensive test coverage for all new components
- Update TODO.md with completed tasks and accurate completion dates
2025-08-04 23:33:28 +03:00

367 lines
9.0 KiB
Go

package errors
import (
"runtime"
"strings"
"testing"
)
func TestGetSuggestions(t *testing.T) {
tests := []struct {
name string
code ErrorCode
context map[string]string
contains []string
}{
{
name: "file not found with path",
code: ErrCodeFileNotFound,
context: map[string]string{
"path": "/path/to/action.yml",
},
contains: []string{
"Check if the file exists: /path/to/action.yml",
"Verify the file path is correct",
"--recursive flag",
},
},
{
name: "file not found action file",
code: ErrCodeFileNotFound,
context: map[string]string{
"path": "/project/action.yml",
},
contains: []string{
"Common action file names: action.yml, action.yaml",
"Check if the file is in a subdirectory",
},
},
{
name: "permission denied",
code: ErrCodePermission,
context: map[string]string{
"path": "/restricted/file.txt",
},
contains: []string{
"Check file permissions: ls -la /restricted/file.txt",
"chmod 644 /restricted/file.txt",
},
},
{
name: "invalid YAML with line number",
code: ErrCodeInvalidYAML,
context: map[string]string{
"line": "25",
},
contains: []string{
"Error near line 25",
"Check YAML indentation",
"use spaces, not tabs",
"YAML validator",
},
},
{
name: "invalid YAML with tab error",
code: ErrCodeInvalidYAML,
context: map[string]string{
"error": "found character that cannot start any token (tab)",
},
contains: []string{
"YAML files must use spaces for indentation, not tabs",
"Replace all tabs with spaces",
},
},
{
name: "invalid action with missing fields",
code: ErrCodeInvalidAction,
context: map[string]string{
"missing_fields": "name, description",
},
contains: []string{
"Missing required fields: name, description",
"required fields: name, description",
"gh-action-readme schema",
},
},
{
name: "no action files",
code: ErrCodeNoActionFiles,
context: map[string]string{
"directory": "/project",
},
contains: []string{
"Current directory: /project",
"find /project -name 'action.y*ml'",
"--recursive flag",
"action.yml or action.yaml",
},
},
{
name: "GitHub API 401 error",
code: ErrCodeGitHubAPI,
context: map[string]string{
"status_code": "401",
},
contains: []string{
"Authentication failed",
"check your GitHub token",
"Token may be expired",
},
},
{
name: "GitHub API 403 error",
code: ErrCodeGitHubAPI,
context: map[string]string{
"status_code": "403",
},
contains: []string{
"Access forbidden",
"check token permissions",
"rate limit",
},
},
{
name: "GitHub API 404 error",
code: ErrCodeGitHubAPI,
context: map[string]string{
"status_code": "404",
},
contains: []string{
"Repository or resource not found",
"repository is private",
},
},
{
name: "GitHub rate limit",
code: ErrCodeGitHubRateLimit,
context: map[string]string{},
contains: []string{
"rate limit exceeded",
"GITHUB_TOKEN",
"gh auth login",
"Rate limits reset every hour",
},
},
{
name: "GitHub auth",
code: ErrCodeGitHubAuth,
context: map[string]string{},
contains: []string{
"export GITHUB_TOKEN",
"gh auth login",
"https://github.com/settings/tokens",
"'repo' scope",
},
},
{
name: "configuration error with path",
code: ErrCodeConfiguration,
context: map[string]string{
"config_path": "~/.config/gh-action-readme/config.yaml",
},
contains: []string{
"Config path: ~/.config/gh-action-readme/config.yaml",
"ls -la ~/.config/gh-action-readme/config.yaml",
"gh-action-readme config init",
},
},
{
name: "validation error with invalid fields",
code: ErrCodeValidation,
context: map[string]string{
"invalid_fields": "runs.using, inputs.test",
},
contains: []string{
"Invalid fields: runs.using, inputs.test",
"Check spelling and nesting",
"gh-action-readme schema",
},
},
{
name: "template error with theme",
code: ErrCodeTemplateRender,
context: map[string]string{
"theme": "custom",
},
contains: []string{
"Current theme: custom",
"Try using a different theme",
"Available themes:",
},
},
{
name: "file write error with output path",
code: ErrCodeFileWrite,
context: map[string]string{
"output_path": "/output/README.md",
},
contains: []string{
"Output directory: /output",
"Check permissions: ls -la /output",
"mkdir -p /output",
},
},
{
name: "dependency analysis error",
code: ErrCodeDependencyAnalysis,
context: map[string]string{
"action": "my-action",
},
contains: []string{
"Analyzing action: my-action",
"GitHub token is set",
"composite actions",
},
},
{
name: "cache access error",
code: ErrCodeCacheAccess,
context: map[string]string{
"cache_path": "~/.cache/gh-action-readme",
},
contains: []string{
"Cache path: ~/.cache/gh-action-readme",
"gh-action-readme cache clear",
"permissions: ls -la ~/.cache/gh-action-readme",
},
},
{
name: "unknown error code",
code: "UNKNOWN_TEST_CODE",
context: map[string]string{},
contains: []string{
"Check the error message",
"--verbose flag",
"project documentation",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
suggestions := GetSuggestions(tt.code, tt.context)
if len(suggestions) == 0 {
t.Error("GetSuggestions() returned empty slice")
return
}
allSuggestions := strings.Join(suggestions, " ")
for _, expected := range tt.contains {
if !strings.Contains(allSuggestions, expected) {
t.Errorf(
"GetSuggestions() missing expected content:\nExpected to contain: %q\nSuggestions:\n%s",
expected,
strings.Join(suggestions, "\n"),
)
}
}
})
}
}
func TestGetPermissionSuggestions_OSSpecific(t *testing.T) {
context := map[string]string{"path": "/test/file"}
suggestions := getPermissionSuggestions(context)
allSuggestions := strings.Join(suggestions, " ")
switch runtime.GOOS {
case "windows":
if !strings.Contains(allSuggestions, "Administrator") {
t.Error("Windows-specific suggestions should mention Administrator")
}
if !strings.Contains(allSuggestions, "Windows file permissions") {
t.Error("Windows-specific suggestions should mention Windows file permissions")
}
default:
if !strings.Contains(allSuggestions, "sudo") {
t.Error("Unix-specific suggestions should mention sudo")
}
if !strings.Contains(allSuggestions, "ls -la") {
t.Error("Unix-specific suggestions should mention ls -la")
}
}
}
func TestGetSuggestions_EmptyContext(t *testing.T) {
// Test that all error codes work with empty context
errorCodes := []ErrorCode{
ErrCodeFileNotFound,
ErrCodePermission,
ErrCodeInvalidYAML,
ErrCodeInvalidAction,
ErrCodeNoActionFiles,
ErrCodeGitHubAPI,
ErrCodeGitHubRateLimit,
ErrCodeGitHubAuth,
ErrCodeConfiguration,
ErrCodeValidation,
ErrCodeTemplateRender,
ErrCodeFileWrite,
ErrCodeDependencyAnalysis,
ErrCodeCacheAccess,
}
for _, code := range errorCodes {
t.Run(string(code), func(t *testing.T) {
suggestions := GetSuggestions(code, map[string]string{})
if len(suggestions) == 0 {
t.Errorf("GetSuggestions(%s, {}) returned empty slice", code)
}
})
}
}
func TestGetFileNotFoundSuggestions_ActionFile(t *testing.T) {
context := map[string]string{
"path": "/project/action.yml",
}
suggestions := getFileNotFoundSuggestions(context)
allSuggestions := strings.Join(suggestions, " ")
// Should suggest common action file names when path contains "action"
if !strings.Contains(allSuggestions, "action.yml, action.yaml") {
t.Error("Should suggest common action file names for action file paths")
}
if !strings.Contains(allSuggestions, "subdirectory") {
t.Error("Should suggest checking subdirectories for action files")
}
}
func TestGetInvalidYAMLSuggestions_TabError(t *testing.T) {
context := map[string]string{
"error": "found character that cannot start any token, tab character",
}
suggestions := getInvalidYAMLSuggestions(context)
allSuggestions := strings.Join(suggestions, " ")
// Should prioritize tab-specific suggestions when error mentions tabs
if !strings.Contains(allSuggestions, "tabs with spaces") {
t.Error("Should provide tab-specific suggestions when error mentions tabs")
}
}
func TestGetGitHubAPISuggestions_StatusCodes(t *testing.T) {
statusCodes := map[string]string{
"401": "Authentication failed",
"403": "Access forbidden",
"404": "not found",
}
for code, expectedText := range statusCodes {
t.Run("status_"+code, func(t *testing.T) {
context := map[string]string{"status_code": code}
suggestions := getGitHubAPISuggestions(context)
allSuggestions := strings.Join(suggestions, " ")
if !strings.Contains(allSuggestions, expectedText) {
t.Errorf("Status code %s suggestions should contain %q", code, expectedText)
}
})
}
}