Files
gh-action-readme/internal/wizard/exporter_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

251 lines
7.2 KiB
Go

package wizard
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"gopkg.in/yaml.v3"
"github.com/ivuorinen/gh-action-readme/internal"
)
func TestConfigExporter_ExportConfig(t *testing.T) {
output := internal.NewColoredOutput(true) // quiet mode for testing
exporter := NewConfigExporter(output)
// Create test config
config := createTestConfig()
// Test YAML export
t.Run("export YAML", testYAMLExport(exporter, config))
// Test JSON export
t.Run("export JSON", testJSONExport(exporter, config))
// Test TOML export
t.Run("export TOML", testTOMLExport(exporter, config))
}
// createTestConfig creates a test configuration for testing.
func createTestConfig() *internal.AppConfig {
return &internal.AppConfig{
Organization: "testorg",
Repository: "testrepo",
Version: "1.0.0",
Theme: "github",
OutputFormat: "md",
OutputDir: ".",
AnalyzeDependencies: true,
ShowSecurityInfo: false,
Variables: map[string]string{"TEST_VAR": "test_value"},
Permissions: map[string]string{"contents": "read"},
RunsOn: []string{"ubuntu-latest"},
}
}
// testYAMLExport tests YAML export functionality.
func testYAMLExport(exporter *ConfigExporter, config *internal.AppConfig) func(*testing.T) {
return func(t *testing.T) {
tempDir := t.TempDir()
outputPath := filepath.Join(tempDir, "config.yaml")
err := exporter.ExportConfig(config, FormatYAML, outputPath)
if err != nil {
t.Fatalf("ExportConfig() error = %v", err)
}
verifyFileExists(t, outputPath)
verifyYAMLContent(t, outputPath, config)
}
}
// testJSONExport tests JSON export functionality.
func testJSONExport(exporter *ConfigExporter, config *internal.AppConfig) func(*testing.T) {
return func(t *testing.T) {
tempDir := t.TempDir()
outputPath := filepath.Join(tempDir, "config.json")
err := exporter.ExportConfig(config, FormatJSON, outputPath)
if err != nil {
t.Fatalf("ExportConfig() error = %v", err)
}
verifyFileExists(t, outputPath)
verifyJSONContent(t, outputPath, config)
}
}
// testTOMLExport tests TOML export functionality.
func testTOMLExport(exporter *ConfigExporter, config *internal.AppConfig) func(*testing.T) {
return func(t *testing.T) {
tempDir := t.TempDir()
outputPath := filepath.Join(tempDir, "config.toml")
err := exporter.ExportConfig(config, FormatTOML, outputPath)
if err != nil {
t.Fatalf("ExportConfig() error = %v", err)
}
verifyFileExists(t, outputPath)
verifyTOMLContent(t, outputPath)
}
}
// verifyFileExists checks that a file exists at the given path.
func verifyFileExists(t *testing.T, outputPath string) {
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
t.Fatal("Expected output file to exist")
}
}
// verifyYAMLContent verifies YAML content is valid and contains expected data.
func verifyYAMLContent(t *testing.T, outputPath string, expected *internal.AppConfig) {
data, err := os.ReadFile(outputPath) // #nosec G304 -- test output path
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
var yamlConfig internal.AppConfig
if err := yaml.Unmarshal(data, &yamlConfig); err != nil {
t.Fatalf("Failed to parse YAML: %v", err)
}
if yamlConfig.Organization != expected.Organization {
t.Errorf("Organization = %v, want %v", yamlConfig.Organization, expected.Organization)
}
if yamlConfig.Theme != expected.Theme {
t.Errorf("Theme = %v, want %v", yamlConfig.Theme, expected.Theme)
}
}
// verifyJSONContent verifies JSON content is valid and contains expected data.
func verifyJSONContent(t *testing.T, outputPath string, expected *internal.AppConfig) {
data, err := os.ReadFile(outputPath) // #nosec G304 -- test output path
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
var jsonConfig internal.AppConfig
if err := json.Unmarshal(data, &jsonConfig); err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
if jsonConfig.Repository != expected.Repository {
t.Errorf("Repository = %v, want %v", jsonConfig.Repository, expected.Repository)
}
if jsonConfig.OutputFormat != expected.OutputFormat {
t.Errorf("OutputFormat = %v, want %v", jsonConfig.OutputFormat, expected.OutputFormat)
}
}
// verifyTOMLContent verifies TOML content contains expected fields.
func verifyTOMLContent(t *testing.T, outputPath string) {
data, err := os.ReadFile(outputPath) // #nosec G304 -- test output path
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
content := string(data)
if !strings.Contains(content, `organization = "testorg"`) {
t.Error("TOML should contain organization field")
}
if !strings.Contains(content, `theme = "github"`) {
t.Error("TOML should contain theme field")
}
}
func TestConfigExporter_sanitizeConfig(t *testing.T) {
output := internal.NewColoredOutput(true)
exporter := NewConfigExporter(output)
config := &internal.AppConfig{
Organization: "testorg",
Repository: "testrepo",
GitHubToken: "ghp_secret_token",
RepoOverrides: map[string]internal.AppConfig{
"test/repo": {Theme: "github"},
},
}
sanitized := exporter.sanitizeConfig(config)
// Verify sensitive data is removed
if sanitized.GitHubToken != "" {
t.Error("Expected GitHubToken to be empty after sanitization")
}
if sanitized.RepoOverrides != nil {
t.Error("Expected RepoOverrides to be nil after sanitization")
}
// Verify non-sensitive data is preserved
if sanitized.Organization != config.Organization {
t.Errorf("Organization = %v, want %v", sanitized.Organization, config.Organization)
}
if sanitized.Repository != config.Repository {
t.Errorf("Repository = %v, want %v", sanitized.Repository, config.Repository)
}
}
func TestConfigExporter_GetSupportedFormats(t *testing.T) {
output := internal.NewColoredOutput(true)
exporter := NewConfigExporter(output)
formats := exporter.GetSupportedFormats()
expectedFormats := []ExportFormat{FormatYAML, FormatJSON, FormatTOML}
if len(formats) != len(expectedFormats) {
t.Errorf("GetSupportedFormats() returned %d formats, want %d", len(formats), len(expectedFormats))
}
// Check that all expected formats are present
formatMap := make(map[ExportFormat]bool)
for _, format := range formats {
formatMap[format] = true
}
for _, expected := range expectedFormats {
if !formatMap[expected] {
t.Errorf("Expected format %v not found in supported formats", expected)
}
}
}
func TestConfigExporter_GetDefaultOutputPath(t *testing.T) {
output := internal.NewColoredOutput(true)
exporter := NewConfigExporter(output)
tests := []struct {
format ExportFormat
expected string
}{
{FormatYAML, "config.yaml"},
{FormatJSON, "config.json"},
{FormatTOML, "config.toml"},
}
for _, tt := range tests {
t.Run(string(tt.format), func(t *testing.T) {
path, err := exporter.GetDefaultOutputPath(tt.format)
if err != nil {
t.Fatalf("GetDefaultOutputPath() error = %v", err)
}
if !strings.HasSuffix(path, tt.expected) {
t.Errorf("GetDefaultOutputPath() = %v, should end with %v", path, tt.expected)
}
})
}
// Test invalid format
t.Run("invalid format", func(t *testing.T) {
_, err := exporter.GetDefaultOutputPath("invalid")
if err == nil {
t.Error("Expected error for invalid format")
}
})
}