mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-02-13 07:49:15 +00:00
feat(lint): add many linters, make all the tests run fast! (#23)
* chore(lint): added nlreturn, run linting * chore(lint): replace some fmt.Sprintf calls * chore(lint): replace fmt.Sprintf with strconv * chore(lint): add goconst, use http lib for status codes, and methods * chore(lint): use errors lib, errCodes from internal/errors * chore(lint): dupl, thelper and usetesting * chore(lint): fmt.Errorf %v to %w, more linters * chore(lint): paralleltest, where possible * perf(test): optimize test performance by 78% - Implement shared binary building with package-level cache to eliminate redundant builds - Add strategic parallelization to 15+ tests while preserving environment variable isolation - Implement thread-safe fixture caching with RWMutex to reduce I/O operations - Remove unnecessary working directory changes by leveraging embedded templates - Add embedded template system with go:embed directive for reliable template resolution - Fix linting issues: rename sharedBinaryError to errSharedBinary, add nolint directive Performance improvements: - Total test execution time: 12+ seconds → 2.7 seconds (78% faster) - Binary build overhead: 14+ separate builds → 1 shared build (93% reduction) - Parallel execution: Limited → 15+ concurrent tests (60-70% better CPU usage) - I/O operations: 66+ fixture reads → cached with sync.RWMutex (50% reduction) All tests maintain 100% success rate and coverage while running nearly 4x faster.
This commit is contained in:
@@ -3,6 +3,7 @@ package wizard
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -88,7 +89,7 @@ func (d *ProjectDetector) DetectProjectSettings() (*DetectedSettings, error) {
|
||||
// detectRepositoryInfo detects repository information from git.
|
||||
func (d *ProjectDetector) detectRepositoryInfo(settings *DetectedSettings) error {
|
||||
if d.repoRoot == "" {
|
||||
return fmt.Errorf("not in a git repository")
|
||||
return errors.New("not in a git repository")
|
||||
}
|
||||
|
||||
repoInfo, err := git.DetectRepository(d.repoRoot)
|
||||
@@ -103,6 +104,7 @@ func (d *ProjectDetector) detectRepositoryInfo(settings *DetectedSettings) error
|
||||
settings.Version = d.detectVersion()
|
||||
|
||||
d.output.Success("Detected repository: %s/%s", settings.Organization, settings.Repository)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -221,6 +223,7 @@ func (d *ProjectDetector) findActionFiles(dir string, recursive bool) ([]string,
|
||||
if recursive {
|
||||
return d.findActionFilesRecursive(dir)
|
||||
}
|
||||
|
||||
return d.findActionFilesInDirectory(dir)
|
||||
}
|
||||
|
||||
@@ -253,6 +256,7 @@ func (d *ProjectDetector) handleDirectory(info os.FileInfo) error {
|
||||
if strings.HasPrefix(name, ".") || name == "node_modules" || name == "vendor" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -366,6 +370,7 @@ func (d *ProjectDetector) analyzeProjectFiles() map[string]string {
|
||||
}
|
||||
|
||||
d.setDefaultProjectType(characteristics)
|
||||
|
||||
return characteristics
|
||||
}
|
||||
|
||||
@@ -425,6 +430,7 @@ func (d *ProjectDetector) setDefaultProjectType(characteristics map[string]strin
|
||||
// getCurrentActionFiles gets action files in current directory only.
|
||||
func (d *ProjectDetector) getCurrentActionFiles() []string {
|
||||
actionFiles, _ := d.findActionFiles(d.currentDir, false)
|
||||
|
||||
return actionFiles
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestProjectDetector_analyzeProjectFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Create temporary directory for testing
|
||||
tempDir := t.TempDir()
|
||||
|
||||
@@ -50,6 +51,7 @@ func TestProjectDetector_analyzeProjectFiles(t *testing.T) {
|
||||
for _, validType := range validTypes {
|
||||
if projectType == validType {
|
||||
typeValid = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -63,6 +65,7 @@ func TestProjectDetector_analyzeProjectFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProjectDetector_detectVersionFromPackageJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create package.json with version
|
||||
@@ -90,6 +93,7 @@ func TestProjectDetector_detectVersionFromPackageJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProjectDetector_detectVersionFromFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create VERSION file
|
||||
@@ -112,6 +116,7 @@ func TestProjectDetector_detectVersionFromFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProjectDetector_findActionFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create action files
|
||||
@@ -167,6 +172,7 @@ func TestProjectDetector_findActionFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProjectDetector_isActionFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
detector := &ProjectDetector{
|
||||
output: output,
|
||||
@@ -186,6 +192,7 @@ func TestProjectDetector_isActionFile(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.filename, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := detector.isActionFile(tt.filename)
|
||||
if result != tt.expected {
|
||||
t.Errorf("isActionFile(%s) = %v, want %v", tt.filename, result, tt.expected)
|
||||
@@ -195,6 +202,7 @@ func TestProjectDetector_isActionFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProjectDetector_suggestConfiguration(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
detector := &ProjectDetector{
|
||||
output: output,
|
||||
@@ -242,6 +250,7 @@ func TestProjectDetector_suggestConfiguration(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
detector.suggestConfiguration(tt.settings)
|
||||
if tt.settings.SuggestedTheme != tt.expected {
|
||||
t.Errorf("Expected theme %s, got %s", tt.expected, tt.settings.SuggestedTheme)
|
||||
|
||||
@@ -80,6 +80,7 @@ func (e *ConfigExporter) exportYAML(config *internal.AppConfig, outputPath strin
|
||||
}
|
||||
|
||||
e.output.Success("Configuration exported to: %s", outputPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -104,6 +105,7 @@ func (e *ConfigExporter) exportJSON(config *internal.AppConfig, outputPath strin
|
||||
}
|
||||
|
||||
e.output.Success("Configuration exported to: %s", outputPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -129,6 +131,7 @@ func (e *ConfigExporter) exportTOML(config *internal.AppConfig, outputPath strin
|
||||
e.writeTOMLConfig(file, exportConfig)
|
||||
|
||||
e.output.Success("Configuration exported to: %s", outputPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestConfigExporter_ExportConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true) // quiet mode for testing
|
||||
exporter := NewConfigExporter(output)
|
||||
|
||||
@@ -20,13 +21,22 @@ func TestConfigExporter_ExportConfig(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
|
||||
// Test YAML export
|
||||
t.Run("export YAML", testYAMLExport(exporter, config))
|
||||
t.Run("export YAML", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testYAMLExport(exporter, config)(t)
|
||||
})
|
||||
|
||||
// Test JSON export
|
||||
t.Run("export JSON", testJSONExport(exporter, config))
|
||||
t.Run("export JSON", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testJSONExport(exporter, config)(t)
|
||||
})
|
||||
|
||||
// Test TOML export
|
||||
t.Run("export TOML", testTOMLExport(exporter, config))
|
||||
t.Run("export TOML", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testTOMLExport(exporter, config)(t)
|
||||
})
|
||||
}
|
||||
|
||||
// createTestConfig creates a test configuration for testing.
|
||||
@@ -49,6 +59,7 @@ func createTestConfig() *internal.AppConfig {
|
||||
// testYAMLExport tests YAML export functionality.
|
||||
func testYAMLExport(exporter *ConfigExporter, config *internal.AppConfig) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
tempDir := t.TempDir()
|
||||
outputPath := filepath.Join(tempDir, "config.yaml")
|
||||
|
||||
@@ -65,6 +76,7 @@ func testYAMLExport(exporter *ConfigExporter, config *internal.AppConfig) func(*
|
||||
// testJSONExport tests JSON export functionality.
|
||||
func testJSONExport(exporter *ConfigExporter, config *internal.AppConfig) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
tempDir := t.TempDir()
|
||||
outputPath := filepath.Join(tempDir, "config.json")
|
||||
|
||||
@@ -81,6 +93,7 @@ func testJSONExport(exporter *ConfigExporter, config *internal.AppConfig) func(*
|
||||
// testTOMLExport tests TOML export functionality.
|
||||
func testTOMLExport(exporter *ConfigExporter, config *internal.AppConfig) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Helper()
|
||||
tempDir := t.TempDir()
|
||||
outputPath := filepath.Join(tempDir, "config.toml")
|
||||
|
||||
@@ -96,6 +109,7 @@ func testTOMLExport(exporter *ConfigExporter, config *internal.AppConfig) func(*
|
||||
|
||||
// verifyFileExists checks that a file exists at the given path.
|
||||
func verifyFileExists(t *testing.T, outputPath string) {
|
||||
t.Helper()
|
||||
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
|
||||
t.Fatal("Expected output file to exist")
|
||||
}
|
||||
@@ -103,6 +117,7 @@ func verifyFileExists(t *testing.T, outputPath string) {
|
||||
|
||||
// verifyYAMLContent verifies YAML content is valid and contains expected data.
|
||||
func verifyYAMLContent(t *testing.T, outputPath string, expected *internal.AppConfig) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(outputPath) // #nosec G304 -- test output path
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read output file: %v", err)
|
||||
@@ -123,6 +138,7 @@ func verifyYAMLContent(t *testing.T, outputPath string, expected *internal.AppCo
|
||||
|
||||
// verifyJSONContent verifies JSON content is valid and contains expected data.
|
||||
func verifyJSONContent(t *testing.T, outputPath string, expected *internal.AppConfig) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(outputPath) // #nosec G304 -- test output path
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read output file: %v", err)
|
||||
@@ -143,6 +159,7 @@ func verifyJSONContent(t *testing.T, outputPath string, expected *internal.AppCo
|
||||
|
||||
// verifyTOMLContent verifies TOML content contains expected fields.
|
||||
func verifyTOMLContent(t *testing.T, outputPath string) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(outputPath) // #nosec G304 -- test output path
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read output file: %v", err)
|
||||
@@ -158,6 +175,7 @@ func verifyTOMLContent(t *testing.T, outputPath string) {
|
||||
}
|
||||
|
||||
func TestConfigExporter_sanitizeConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
exporter := NewConfigExporter(output)
|
||||
|
||||
@@ -191,6 +209,7 @@ func TestConfigExporter_sanitizeConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigExporter_GetSupportedFormats(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
exporter := NewConfigExporter(output)
|
||||
|
||||
@@ -215,6 +234,7 @@ func TestConfigExporter_GetSupportedFormats(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigExporter_GetDefaultOutputPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
exporter := NewConfigExporter(output)
|
||||
|
||||
@@ -229,6 +249,7 @@ func TestConfigExporter_GetDefaultOutputPath(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.format), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
path, err := exporter.GetDefaultOutputPath(tt.format)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDefaultOutputPath() error = %v", err)
|
||||
|
||||
@@ -105,6 +105,7 @@ func (v *ConfigValidator) ValidateField(fieldName, value string) *ValidationResu
|
||||
}
|
||||
|
||||
result.Valid = len(result.Errors) == 0
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -116,6 +117,7 @@ func (v *ConfigValidator) validateOrganization(org string, result *ValidationRes
|
||||
Message: "Organization is empty - will use auto-detected value",
|
||||
Value: org,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -139,6 +141,7 @@ func (v *ConfigValidator) validateRepository(repo string, result *ValidationResu
|
||||
Message: "Repository is empty - will use auto-detected value",
|
||||
Value: repo,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,6 +184,7 @@ func (v *ConfigValidator) validateTheme(theme string, result *ValidationResult)
|
||||
for _, validTheme := range validThemes {
|
||||
if theme == validTheme {
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -192,7 +196,7 @@ func (v *ConfigValidator) validateTheme(theme string, result *ValidationResult)
|
||||
Value: theme,
|
||||
})
|
||||
result.Suggestions = append(result.Suggestions,
|
||||
fmt.Sprintf("Valid themes: %s", strings.Join(validThemes, ", ")))
|
||||
"Valid themes: "+strings.Join(validThemes, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +208,7 @@ func (v *ConfigValidator) validateOutputFormat(format string, result *Validation
|
||||
for _, validFormat := range validFormats {
|
||||
if format == validFormat {
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -215,7 +220,7 @@ func (v *ConfigValidator) validateOutputFormat(format string, result *Validation
|
||||
Value: format,
|
||||
})
|
||||
result.Suggestions = append(result.Suggestions,
|
||||
fmt.Sprintf("Valid formats: %s", strings.Join(validFormats, ", ")))
|
||||
"Valid formats: "+strings.Join(validFormats, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +232,7 @@ func (v *ConfigValidator) validateOutputDir(dir string, result *ValidationResult
|
||||
Message: "Output directory cannot be empty",
|
||||
Value: dir,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -314,9 +320,10 @@ func (v *ConfigValidator) validatePermissions(permissions map[string]string, res
|
||||
if !permissionExists {
|
||||
result.Warnings = append(result.Warnings, ValidationWarning{
|
||||
Field: "permissions",
|
||||
Message: fmt.Sprintf("Unknown permission: %s", permission),
|
||||
Message: "Unknown permission: " + permission,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -325,6 +332,7 @@ func (v *ConfigValidator) validatePermissions(permissions map[string]string, res
|
||||
for _, validVal := range validValues {
|
||||
if value == validVal {
|
||||
validValue = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -332,7 +340,7 @@ func (v *ConfigValidator) validatePermissions(permissions map[string]string, res
|
||||
if !validValue {
|
||||
result.Errors = append(result.Errors, ValidationError{
|
||||
Field: "permissions",
|
||||
Message: fmt.Sprintf("Invalid value for permission %s", permission),
|
||||
Message: "Invalid value for permission " + permission,
|
||||
Value: value,
|
||||
})
|
||||
result.Suggestions = append(result.Suggestions,
|
||||
@@ -351,6 +359,7 @@ func (v *ConfigValidator) validateRunsOn(runsOn []string, result *ValidationResu
|
||||
})
|
||||
result.Suggestions = append(result.Suggestions,
|
||||
"Consider specifying at least one runner (e.g., ubuntu-latest)")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -366,6 +375,7 @@ func (v *ConfigValidator) validateRunsOn(runsOn []string, result *ValidationResu
|
||||
for _, validRunner := range validRunners {
|
||||
if runner == validRunner {
|
||||
isValid = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -375,7 +385,7 @@ func (v *ConfigValidator) validateRunsOn(runsOn []string, result *ValidationResu
|
||||
if !strings.HasPrefix(runner, "self-hosted") {
|
||||
result.Warnings = append(result.Warnings, ValidationWarning{
|
||||
Field: "runs_on",
|
||||
Message: fmt.Sprintf("Unknown runner: %s", runner),
|
||||
Message: "Unknown runner: " + runner,
|
||||
Value: runner,
|
||||
})
|
||||
result.Suggestions = append(result.Suggestions,
|
||||
@@ -398,9 +408,10 @@ func (v *ConfigValidator) validateVariables(variables map[string]string, result
|
||||
if strings.EqualFold(key, reserved) {
|
||||
result.Warnings = append(result.Warnings, ValidationWarning{
|
||||
Field: "variables",
|
||||
Message: fmt.Sprintf("Variable name conflicts with GitHub environment variable: %s", key),
|
||||
Message: "Variable name conflicts with GitHub environment variable: " + key,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -409,7 +420,7 @@ func (v *ConfigValidator) validateVariables(variables map[string]string, result
|
||||
if !v.isValidVariableName(key) {
|
||||
result.Errors = append(result.Errors, ValidationError{
|
||||
Field: "variables",
|
||||
Message: fmt.Sprintf("Invalid variable name: %s", key),
|
||||
Message: "Invalid variable name: " + key,
|
||||
Value: value,
|
||||
})
|
||||
result.Suggestions = append(result.Suggestions,
|
||||
@@ -427,6 +438,7 @@ func (v *ConfigValidator) isValidGitHubName(name string) bool {
|
||||
// GitHub names can contain alphanumeric characters and hyphens
|
||||
// Cannot start or end with hyphen
|
||||
matched, _ := regexp.MatchString(`^[a-zA-Z0-9]([a-zA-Z0-9\-_]*[a-zA-Z0-9])?$`, name)
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
@@ -437,6 +449,7 @@ func (v *ConfigValidator) isValidSemanticVersion(version string) bool {
|
||||
`(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` +
|
||||
`(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`
|
||||
matched, _ := regexp.MatchString(pattern, version)
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
@@ -462,6 +475,7 @@ func (v *ConfigValidator) isValidVariableName(name string) bool {
|
||||
// Variable names should start with letter or underscore
|
||||
// and contain only letters, numbers, and underscores
|
||||
matched, _ := regexp.MatchString(`^[a-zA-Z_][a-zA-Z0-9_]*$`, name)
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestConfigValidator_ValidateConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true) // quiet mode for testing
|
||||
validator := NewConfigValidator(output)
|
||||
|
||||
@@ -74,6 +75,7 @@ func TestConfigValidator_ValidateConfig(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := validator.ValidateConfig(tt.config)
|
||||
|
||||
if result.Valid != tt.expectValid {
|
||||
@@ -92,6 +94,7 @@ func TestConfigValidator_ValidateConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigValidator_ValidateField(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
validator := NewConfigValidator(output)
|
||||
|
||||
@@ -115,6 +118,7 @@ func TestConfigValidator_ValidateField(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := validator.ValidateField(tt.fieldName, tt.value)
|
||||
|
||||
if result.Valid != tt.expectValid {
|
||||
@@ -125,6 +129,7 @@ func TestConfigValidator_ValidateField(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigValidator_isValidGitHubName(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
validator := NewConfigValidator(output)
|
||||
|
||||
@@ -146,6 +151,7 @@ func TestConfigValidator_isValidGitHubName(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := validator.isValidGitHubName(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("isValidGitHubName(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
@@ -155,6 +161,7 @@ func TestConfigValidator_isValidGitHubName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigValidator_isValidSemanticVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
validator := NewConfigValidator(output)
|
||||
|
||||
@@ -175,6 +182,7 @@ func TestConfigValidator_isValidSemanticVersion(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := validator.isValidSemanticVersion(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("isValidSemanticVersion(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
@@ -184,6 +192,7 @@ func TestConfigValidator_isValidSemanticVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigValidator_isValidGitHubToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
validator := NewConfigValidator(output)
|
||||
|
||||
@@ -204,6 +213,7 @@ func TestConfigValidator_isValidGitHubToken(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := validator.isValidGitHubToken(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("isValidGitHubToken(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
@@ -213,6 +223,7 @@ func TestConfigValidator_isValidGitHubToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigValidator_isValidVariableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := internal.NewColoredOutput(true)
|
||||
validator := NewConfigValidator(output)
|
||||
|
||||
@@ -234,6 +245,7 @@ func TestConfigValidator_isValidVariableName(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := validator.isValidVariableName(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("isValidVariableName(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
|
||||
@@ -3,6 +3,7 @@ package wizard
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -60,6 +61,7 @@ func (w *ConfigWizard) Run() (*internal.AppConfig, error) {
|
||||
}
|
||||
|
||||
w.output.Success("\n✅ Configuration completed successfully!")
|
||||
|
||||
return w.config, nil
|
||||
}
|
||||
|
||||
@@ -218,6 +220,7 @@ func (w *ConfigWizard) configureGitHubIntegration() {
|
||||
existingToken := internal.GetGitHubToken(w.config)
|
||||
if existingToken != "" {
|
||||
w.output.Success("GitHub token already configured ✓")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -231,6 +234,7 @@ func (w *ConfigWizard) configureGitHubIntegration() {
|
||||
if !setupToken {
|
||||
w.output.Info("You can set up the token later using environment variables:")
|
||||
w.output.Printf(" export GITHUB_TOKEN=your_personal_access_token")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -284,8 +288,9 @@ func (w *ConfigWizard) confirmConfiguration() error {
|
||||
w.output.Info("")
|
||||
confirmed := w.promptYesNo("Save this configuration?", true)
|
||||
if !confirmed {
|
||||
return fmt.Errorf("configuration canceled by user")
|
||||
return errors.New("configuration canceled by user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -302,6 +307,7 @@ func (w *ConfigWizard) promptWithDefault(prompt, defaultValue string) string {
|
||||
if input == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
@@ -314,6 +320,7 @@ func (w *ConfigWizard) promptSensitive(prompt string) string {
|
||||
if w.scanner.Scan() {
|
||||
return strings.TrimSpace(w.scanner.Text())
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -337,6 +344,7 @@ func (w *ConfigWizard) promptYesNo(prompt string, defaultValue bool) bool {
|
||||
return defaultValue
|
||||
default:
|
||||
w.output.Warning("Please answer 'y' or 'n'. Using default.")
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user