Files
gh-action-readme/internal/wizard/detector_test.go
Ismo Vuorinen f94967713a refactor: major codebase improvements and test framework overhaul
This commit represents a comprehensive refactoring of the codebase focused on
improving code quality, testability, and maintainability.

Key improvements:
- Implement dependency injection and interface-based architecture
- Add comprehensive test framework with fixtures and test suites
- Fix all linting issues (errcheck, gosec, staticcheck, goconst, etc.)
- Achieve full EditorConfig compliance across all files
- Replace hardcoded test data with proper fixture files
- Add configuration loader with hierarchical config support
- Improve error handling with contextual information
- Add progress indicators for better user feedback
- Enhance Makefile with help system and improved editorconfig commands
- Consolidate constants and remove deprecated code
- Strengthen validation logic for GitHub Actions
- Add focused consumer interfaces for better separation of concerns

Testing improvements:
- Add comprehensive integration tests
- Implement test executor pattern for better test organization
- Create extensive YAML fixture library for testing
- Fix all failing tests and improve test coverage
- Add validation test fixtures to avoid embedded YAML in Go files

Build and tooling:
- Update Makefile to show help by default
- Fix editorconfig commands to use eclint properly
- Add comprehensive help documentation to all make targets
- Improve file selection patterns to avoid glob errors

This refactoring maintains backward compatibility while significantly
improving the internal architecture and developer experience.
2025-08-05 23:20:58 +03:00

252 lines
6.2 KiB
Go

package wizard
import (
"os"
"path/filepath"
"testing"
"github.com/ivuorinen/gh-action-readme/internal"
)
func TestProjectDetector_analyzeProjectFiles(t *testing.T) {
// Create temporary directory for testing
tempDir := t.TempDir()
// Create test files (go.mod should be processed last to be the final language)
testFiles := map[string]string{
"Dockerfile": "FROM alpine",
"action.yml": "name: Test Action",
"next.config.js": "module.exports = {}",
"package.json": `{"name": "test", "version": "1.0.0"}`,
"go.mod": "module test", // This should be detected last
}
for filename, content := range testFiles {
filePath := filepath.Join(tempDir, filename)
if err := os.WriteFile(filePath, []byte(content), 0600); err != nil { // #nosec G306 -- test file permissions
t.Fatalf("Failed to create test file %s: %v", filename, err)
}
}
// Create detector with temp directory
output := internal.NewColoredOutput(true)
detector := &ProjectDetector{
output: output,
currentDir: tempDir,
}
characteristics := detector.analyzeProjectFiles()
// Test that a language is detected (either Go or JavaScript/TypeScript is valid)
language := characteristics["language"]
if language != "Go" && language != "JavaScript/TypeScript" {
t.Errorf("Expected language 'Go' or 'JavaScript/TypeScript', got '%s'", language)
}
// Test that appropriate type is detected
projectType := characteristics["type"]
validTypes := []string{"Go Module", "Node.js Project"}
typeValid := false
for _, validType := range validTypes {
if projectType == validType {
typeValid = true
break
}
}
if !typeValid {
t.Errorf("Expected type to be one of %v, got '%s'", validTypes, projectType)
}
if characteristics["framework"] != "Next.js" {
t.Errorf("Expected framework 'Next.js', got '%s'", characteristics["framework"])
}
}
func TestProjectDetector_detectVersionFromPackageJSON(t *testing.T) {
tempDir := t.TempDir()
// Create package.json with version
packageJSON := `{
"name": "test-package",
"version": "2.1.0",
"description": "Test package"
}`
packagePath := filepath.Join(tempDir, "package.json")
if err := os.WriteFile(packagePath, []byte(packageJSON), 0600); err != nil { // #nosec G306 -- test file permissions
t.Fatalf("Failed to create package.json: %v", err)
}
output := internal.NewColoredOutput(true)
detector := &ProjectDetector{
output: output,
currentDir: tempDir,
}
version := detector.detectVersionFromPackageJSON()
if version != "2.1.0" {
t.Errorf("Expected version '2.1.0', got '%s'", version)
}
}
func TestProjectDetector_detectVersionFromFiles(t *testing.T) {
tempDir := t.TempDir()
// Create VERSION file
versionContent := "3.2.1\n"
versionPath := filepath.Join(tempDir, "VERSION")
if err := os.WriteFile(versionPath, []byte(versionContent), 0600); err != nil { // #nosec G306 -- test file permissions
t.Fatalf("Failed to create VERSION file: %v", err)
}
output := internal.NewColoredOutput(true)
detector := &ProjectDetector{
output: output,
currentDir: tempDir,
}
version := detector.detectVersionFromFiles()
if version != "3.2.1" {
t.Errorf("Expected version '3.2.1', got '%s'", version)
}
}
func TestProjectDetector_findActionFiles(t *testing.T) {
tempDir := t.TempDir()
// Create action files
actionYML := filepath.Join(tempDir, "action.yml")
if err := os.WriteFile(
actionYML,
[]byte("name: Test Action"),
0600, // #nosec G306 -- test file permissions
); err != nil {
t.Fatalf("Failed to create action.yml: %v", err)
}
// Create subdirectory with another action file
subDir := filepath.Join(tempDir, "subaction")
if err := os.MkdirAll(subDir, 0750); err != nil { // #nosec G301 -- test directory permissions
t.Fatalf("Failed to create subdirectory: %v", err)
}
subActionYAML := filepath.Join(subDir, "action.yaml")
if err := os.WriteFile(
subActionYAML,
[]byte("name: Sub Action"),
0600, // #nosec G306 -- test file permissions
); err != nil {
t.Fatalf("Failed to create sub action.yaml: %v", err)
}
output := internal.NewColoredOutput(true)
detector := &ProjectDetector{
output: output,
currentDir: tempDir,
}
// Test non-recursive
files, err := detector.findActionFiles(tempDir, false)
if err != nil {
t.Fatalf("findActionFiles() error = %v", err)
}
if len(files) != 1 {
t.Errorf("Expected 1 action file, got %d", len(files))
}
// Test recursive
files, err = detector.findActionFiles(tempDir, true)
if err != nil {
t.Fatalf("findActionFiles() error = %v", err)
}
if len(files) != 2 {
t.Errorf("Expected 2 action files, got %d", len(files))
}
}
func TestProjectDetector_isActionFile(t *testing.T) {
output := internal.NewColoredOutput(true)
detector := &ProjectDetector{
output: output,
}
tests := []struct {
filename string
expected bool
}{
{"action.yml", true},
{"action.yaml", true},
{"Action.yml", false},
{"action.yml.bak", false},
{"other.yml", false},
{"readme.md", false},
}
for _, tt := range tests {
t.Run(tt.filename, func(t *testing.T) {
result := detector.isActionFile(tt.filename)
if result != tt.expected {
t.Errorf("isActionFile(%s) = %v, want %v", tt.filename, result, tt.expected)
}
})
}
}
func TestProjectDetector_suggestConfiguration(t *testing.T) {
output := internal.NewColoredOutput(true)
detector := &ProjectDetector{
output: output,
}
tests := []struct {
name string
settings *DetectedSettings
expected string
}{
{
name: "composite action",
settings: &DetectedSettings{
HasCompositeAction: true,
},
expected: "professional",
},
{
name: "with dockerfile",
settings: &DetectedSettings{
HasDockerfile: true,
},
expected: "github",
},
{
name: "go project",
settings: &DetectedSettings{
Language: "Go",
},
expected: "minimal",
},
{
name: "with framework",
settings: &DetectedSettings{
Framework: "Next.js",
},
expected: "github",
},
{
name: "default case",
settings: &DetectedSettings{},
expected: "default",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
detector.suggestConfiguration(tt.settings)
if tt.settings.SuggestedTheme != tt.expected {
t.Errorf("Expected theme %s, got %s", tt.expected, tt.settings.SuggestedTheme)
}
})
}
}