mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-01-26 11:14:04 +00:00
* feat: rename internal/errors to internal/apperrors * fix(tests): clear env values before using in tests * feat: rename internal/errors to internal/apperrors * chore(deps): update go and all dependencies * chore: remove renovate from pre-commit, formatting * chore: sonarcloud fixes * feat: consolidate constants to appconstants/constants.go * chore: sonarcloud fixes * feat: simplification, deduplication, test utils * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: clean up * fix: config discovery, const deduplication * chore: fixes
346 lines
8.5 KiB
Go
346 lines
8.5 KiB
Go
package apperrors
|
|
|
|
import (
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/ivuorinen/gh-action-readme/appconstants"
|
|
"github.com/ivuorinen/gh-action-readme/testutil"
|
|
)
|
|
|
|
// Test helper factories for creating context maps
|
|
|
|
func ctxPath(path string) map[string]string {
|
|
return map[string]string{"path": path}
|
|
}
|
|
|
|
func ctxError(err string) map[string]string {
|
|
return map[string]string{"error": err}
|
|
}
|
|
|
|
func ctxStatusCode(code string) map[string]string {
|
|
return map[string]string{"status_code": code}
|
|
}
|
|
|
|
func ctxEmpty() map[string]string {
|
|
return map[string]string{}
|
|
}
|
|
|
|
func TestGetSuggestions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
code appconstants.ErrorCode
|
|
context map[string]string
|
|
contains []string
|
|
}{
|
|
{
|
|
name: "file not found with path",
|
|
code: appconstants.ErrCodeFileNotFound,
|
|
context: ctxPath("/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: appconstants.ErrCodeFileNotFound,
|
|
context: ctxPath("/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: appconstants.ErrCodePermission,
|
|
context: ctxPath("/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: appconstants.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: appconstants.ErrCodeInvalidYAML,
|
|
context: ctxError("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: appconstants.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: appconstants.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: appconstants.ErrCodeGitHubAPI,
|
|
context: ctxStatusCode("401"),
|
|
contains: []string{
|
|
"Authentication failed",
|
|
"check your GitHub token",
|
|
"Token may be expired",
|
|
},
|
|
},
|
|
{
|
|
name: "GitHub API 403 error",
|
|
code: appconstants.ErrCodeGitHubAPI,
|
|
context: ctxStatusCode("403"),
|
|
contains: []string{
|
|
"Access forbidden",
|
|
"check token permissions",
|
|
"rate limit",
|
|
},
|
|
},
|
|
{
|
|
name: "GitHub API 404 error",
|
|
code: appconstants.ErrCodeGitHubAPI,
|
|
context: ctxStatusCode("404"),
|
|
contains: []string{
|
|
"Repository or resource not found",
|
|
"repository is private",
|
|
},
|
|
},
|
|
{
|
|
name: "GitHub rate limit",
|
|
code: appconstants.ErrCodeGitHubRateLimit,
|
|
context: ctxEmpty(),
|
|
contains: []string{
|
|
"rate limit exceeded",
|
|
"GITHUB_TOKEN",
|
|
"gh auth login",
|
|
"Rate limits reset every hour",
|
|
},
|
|
},
|
|
{
|
|
name: "GitHub auth",
|
|
code: appconstants.ErrCodeGitHubAuth,
|
|
context: ctxEmpty(),
|
|
contains: []string{
|
|
"export GITHUB_TOKEN",
|
|
"gh auth login",
|
|
"https://github.com/settings/tokens",
|
|
"'repo' scope",
|
|
},
|
|
},
|
|
{
|
|
name: "configuration error with path",
|
|
code: appconstants.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: appconstants.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: appconstants.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: appconstants.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: appconstants.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: appconstants.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: ctxEmpty(),
|
|
contains: []string{
|
|
"Check the error message",
|
|
"--verbose flag",
|
|
"project documentation",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
suggestions := GetSuggestions(tt.code, tt.context)
|
|
testutil.AssertSliceContainsAll(t, suggestions, tt.contains)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPermissionSuggestionsOSSpecific(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
context := map[string]string{"path": "/test/file"}
|
|
suggestions := getPermissionSuggestions(context)
|
|
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
testutil.AssertSliceContainsAll(t, suggestions, []string{"Administrator", "Windows file permissions"})
|
|
default:
|
|
testutil.AssertSliceContainsAll(t, suggestions, []string{"sudo", "ls -la"})
|
|
}
|
|
}
|
|
|
|
func TestGetSuggestionsEmptyContext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test that all error codes work with empty context
|
|
errorCodes := []appconstants.ErrorCode{
|
|
appconstants.ErrCodeFileNotFound,
|
|
appconstants.ErrCodePermission,
|
|
appconstants.ErrCodeInvalidYAML,
|
|
appconstants.ErrCodeInvalidAction,
|
|
appconstants.ErrCodeNoActionFiles,
|
|
appconstants.ErrCodeGitHubAPI,
|
|
appconstants.ErrCodeGitHubRateLimit,
|
|
appconstants.ErrCodeGitHubAuth,
|
|
appconstants.ErrCodeConfiguration,
|
|
appconstants.ErrCodeValidation,
|
|
appconstants.ErrCodeTemplateRender,
|
|
appconstants.ErrCodeFileWrite,
|
|
appconstants.ErrCodeDependencyAnalysis,
|
|
appconstants.ErrCodeCacheAccess,
|
|
}
|
|
|
|
for _, code := range errorCodes {
|
|
t.Run(string(code), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
suggestions := GetSuggestions(code, map[string]string{})
|
|
if len(suggestions) == 0 {
|
|
t.Errorf("GetSuggestions(%s, {}) returned empty slice", code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetFileNotFoundSuggestionsActionFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
context := map[string]string{
|
|
"path": "/project/action.yml",
|
|
}
|
|
|
|
suggestions := getFileNotFoundSuggestions(context)
|
|
testutil.AssertSliceContainsAll(t, suggestions, []string{"action.yml, action.yaml", "subdirectory"})
|
|
}
|
|
|
|
func TestGetInvalidYAMLSuggestionsTabError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
context := map[string]string{
|
|
"error": "found character that cannot start any token, tab character",
|
|
}
|
|
|
|
suggestions := getInvalidYAMLSuggestions(context)
|
|
testutil.AssertSliceContainsAll(t, suggestions, []string{"tabs with spaces"})
|
|
}
|
|
|
|
func TestGetGitHubAPISuggestionsStatusCodes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
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) {
|
|
t.Parallel()
|
|
|
|
context := map[string]string{"status_code": code}
|
|
suggestions := getGitHubAPISuggestions(context)
|
|
testutil.AssertSliceContainsAll(t, suggestions, []string{expectedText})
|
|
})
|
|
}
|
|
}
|