Files
gh-action-readme/internal/config_test.go
Ismo Vuorinen ce02d36929 feat: add comprehensive security scanning and EditorConfig integration
- Add govulncheck, Snyk, and Trivy vulnerability scanning
- Create security workflow for automated scanning on push/PR/schedule
- Add gitleaks for secrets detection and prevention
- Implement EditorConfig linting with eclint and editorconfig-checker
- Update Makefile with security and formatting targets
- Create SECURITY.md with vulnerability reporting guidelines
- Configure Dependabot for automated dependency updates
- Fix all EditorConfig violations across codebase
- Update Go version to 1.23.10 to address stdlib vulnerabilities
- Add tests for internal/helpers package (80% coverage)
- Remove deprecated functions and migrate to error-returning patterns
- Fix YAML indentation in test fixtures to resolve test failures
2025-08-03 20:12:18 +03:00

579 lines
15 KiB
Go

package internal
import (
"os"
"path/filepath"
"testing"
"github.com/ivuorinen/gh-action-readme/testutil"
)
func TestInitConfig(t *testing.T) {
// Save original environment
originalXDGConfig := os.Getenv("XDG_CONFIG_HOME")
originalHome := os.Getenv("HOME")
defer func() {
if originalXDGConfig != "" {
_ = os.Setenv("XDG_CONFIG_HOME", originalXDGConfig)
} else {
_ = os.Unsetenv("XDG_CONFIG_HOME")
}
if originalHome != "" {
_ = os.Setenv("HOME", originalHome)
}
}()
tests := []struct {
name string
configFile string
setupFunc func(t *testing.T, tempDir string)
expectError bool
expected *AppConfig
}{
{
name: "default config when no file exists",
configFile: "",
setupFunc: nil,
expected: &AppConfig{
Theme: "default",
OutputFormat: "md",
OutputDir: ".",
Template: "templates/readme.tmpl",
Schema: "schemas/schema.json",
Verbose: false,
Quiet: false,
GitHubToken: "",
},
},
{
name: "custom config file",
configFile: "custom-config.yml",
setupFunc: func(t *testing.T, tempDir string) {
configPath := filepath.Join(tempDir, "custom-config.yml")
testutil.WriteTestFile(t, configPath, testutil.CustomConfigYAML)
},
expected: &AppConfig{
Theme: "professional",
OutputFormat: "html",
OutputDir: "docs",
Template: "custom-template.tmpl",
Schema: "custom-schema.json",
Verbose: true,
Quiet: false,
GitHubToken: "test-token-from-config",
},
},
{
name: "invalid config file",
configFile: "config.yml",
setupFunc: func(t *testing.T, tempDir string) {
configPath := filepath.Join(tempDir, "config.yml")
testutil.WriteTestFile(t, configPath, "invalid: yaml: content: [")
},
expectError: true,
},
{
name: "nonexistent config file",
configFile: "nonexistent.yml",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, cleanup := testutil.TempDir(t)
defer cleanup()
// Set XDG_CONFIG_HOME to our temp directory
_ = os.Setenv("XDG_CONFIG_HOME", tmpDir)
_ = os.Setenv("HOME", tmpDir)
if tt.setupFunc != nil {
tt.setupFunc(t, tmpDir)
}
// Set config file path if specified
configPath := ""
if tt.configFile != "" {
configPath = filepath.Join(tmpDir, tt.configFile)
}
config, err := InitConfig(configPath)
if tt.expectError {
testutil.AssertError(t, err)
return
}
testutil.AssertNoError(t, err)
// Verify config values
if tt.expected != nil {
testutil.AssertEqual(t, tt.expected.Theme, config.Theme)
testutil.AssertEqual(t, tt.expected.OutputFormat, config.OutputFormat)
testutil.AssertEqual(t, tt.expected.OutputDir, config.OutputDir)
testutil.AssertEqual(t, tt.expected.Template, config.Template)
testutil.AssertEqual(t, tt.expected.Schema, config.Schema)
testutil.AssertEqual(t, tt.expected.Verbose, config.Verbose)
testutil.AssertEqual(t, tt.expected.Quiet, config.Quiet)
testutil.AssertEqual(t, tt.expected.GitHubToken, config.GitHubToken)
}
})
}
}
func TestLoadConfiguration(t *testing.T) {
tests := []struct {
name string
setupFunc func(t *testing.T, tempDir string) (configFile, repoRoot, currentDir string)
expectError bool
checkFunc func(t *testing.T, config *AppConfig)
}{
{
name: "multi-level config hierarchy",
setupFunc: func(t *testing.T, tempDir string) (string, string, string) {
// Create global config
globalConfigDir := filepath.Join(tempDir, ".config", "gh-action-readme")
_ = os.MkdirAll(globalConfigDir, 0755)
globalConfigPath := filepath.Join(globalConfigDir, "config.yaml")
testutil.WriteTestFile(t, globalConfigPath, `
theme: default
output_format: md
github_token: global-token
`)
// Create repo root with repo-specific config
repoRoot := filepath.Join(tempDir, "repo")
_ = os.MkdirAll(repoRoot, 0755)
testutil.WriteTestFile(t, filepath.Join(repoRoot, ".ghreadme.yaml"), `
theme: github
output_format: html
`)
// Create current directory with action-specific config
currentDir := filepath.Join(repoRoot, "action")
_ = os.MkdirAll(currentDir, 0755)
testutil.WriteTestFile(t, filepath.Join(currentDir, "config.yaml"), `
theme: professional
output_dir: output
`)
return globalConfigPath, repoRoot, currentDir
},
checkFunc: func(t *testing.T, config *AppConfig) {
// Should have action-level overrides
testutil.AssertEqual(t, "professional", config.Theme)
testutil.AssertEqual(t, "output", config.OutputDir)
// Should inherit from repo level
testutil.AssertEqual(t, "html", config.OutputFormat)
// Should inherit GitHub token from global config
testutil.AssertEqual(t, "global-token", config.GitHubToken)
},
},
{
name: "environment variable overrides",
setupFunc: func(t *testing.T, tempDir string) (string, string, string) {
// Set environment variables
_ = os.Setenv("GH_README_GITHUB_TOKEN", "env-token")
_ = os.Setenv("GITHUB_TOKEN", "fallback-token")
// Create config file
configPath := filepath.Join(tempDir, "config.yml")
testutil.WriteTestFile(t, configPath, `
theme: minimal
github_token: config-token
`)
t.Cleanup(func() {
_ = os.Unsetenv("GH_README_GITHUB_TOKEN")
_ = os.Unsetenv("GITHUB_TOKEN")
})
return configPath, tempDir, tempDir
},
checkFunc: func(t *testing.T, config *AppConfig) {
// Environment variable should override config file
testutil.AssertEqual(t, "env-token", config.GitHubToken)
testutil.AssertEqual(t, "minimal", config.Theme)
},
},
{
name: "XDG compliance",
setupFunc: func(t *testing.T, tempDir string) (string, string, string) {
// Set XDG environment variables
xdgConfigHome := filepath.Join(tempDir, "xdg-config")
_ = os.Setenv("XDG_CONFIG_HOME", xdgConfigHome)
// Create XDG-compliant config
configDir := filepath.Join(xdgConfigHome, "gh-action-readme")
_ = os.MkdirAll(configDir, 0755)
configPath := filepath.Join(configDir, "config.yaml")
testutil.WriteTestFile(t, configPath, `
theme: github
verbose: true
`)
t.Cleanup(func() {
_ = os.Unsetenv("XDG_CONFIG_HOME")
})
return configPath, tempDir, tempDir
},
checkFunc: func(t *testing.T, config *AppConfig) {
testutil.AssertEqual(t, "github", config.Theme)
testutil.AssertEqual(t, true, config.Verbose)
},
},
{
name: "hidden config file discovery",
setupFunc: func(t *testing.T, tempDir string) (string, string, string) {
repoRoot := filepath.Join(tempDir, "repo")
_ = os.MkdirAll(repoRoot, 0755)
// Create multiple hidden config files
testutil.WriteTestFile(t, filepath.Join(repoRoot, ".ghreadme.yaml"), `
theme: minimal
output_format: json
`)
testutil.WriteTestFile(t, filepath.Join(repoRoot, ".config", "ghreadme.yaml"), `
theme: professional
quiet: true
`)
testutil.WriteTestFile(t, filepath.Join(repoRoot, ".github", "ghreadme.yaml"), `
theme: github
verbose: true
`)
return "", repoRoot, repoRoot
},
checkFunc: func(t *testing.T, config *AppConfig) {
// Should use the first found config (.ghreadme.yaml has priority)
testutil.AssertEqual(t, "minimal", config.Theme)
testutil.AssertEqual(t, "json", config.OutputFormat)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, cleanup := testutil.TempDir(t)
defer cleanup()
// Set HOME to temp directory for fallback
originalHome := os.Getenv("HOME")
_ = os.Setenv("HOME", tmpDir)
defer func() {
if originalHome != "" {
_ = os.Setenv("HOME", originalHome)
} else {
_ = os.Unsetenv("HOME")
}
}()
configFile, repoRoot, currentDir := tt.setupFunc(t, tmpDir)
config, err := LoadConfiguration(configFile, repoRoot, currentDir)
if tt.expectError {
testutil.AssertError(t, err)
return
}
testutil.AssertNoError(t, err)
if tt.checkFunc != nil {
tt.checkFunc(t, config)
}
})
}
}
func TestGetConfigPath(t *testing.T) {
// Save original environment
originalXDGConfig := os.Getenv("XDG_CONFIG_HOME")
originalHome := os.Getenv("HOME")
defer func() {
if originalXDGConfig != "" {
_ = os.Setenv("XDG_CONFIG_HOME", originalXDGConfig)
} else {
_ = os.Unsetenv("XDG_CONFIG_HOME")
}
if originalHome != "" {
_ = os.Setenv("HOME", originalHome)
}
}()
tests := []struct {
name string
setupFunc func(t *testing.T, tempDir string)
contains string
}{
{
name: "XDG_CONFIG_HOME set",
setupFunc: func(_ *testing.T, tempDir string) {
_ = os.Setenv("XDG_CONFIG_HOME", tempDir)
_ = os.Unsetenv("HOME")
},
contains: "gh-action-readme",
},
{
name: "HOME fallback",
setupFunc: func(_ *testing.T, tempDir string) {
_ = os.Unsetenv("XDG_CONFIG_HOME")
_ = os.Setenv("HOME", tempDir)
},
contains: ".config",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, cleanup := testutil.TempDir(t)
defer cleanup()
tt.setupFunc(t, tmpDir)
path, err := GetConfigPath()
testutil.AssertNoError(t, err)
if !filepath.IsAbs(path) {
t.Errorf("expected absolute path, got: %s", path)
}
testutil.AssertStringContains(t, path, tt.contains)
})
}
}
func TestWriteDefaultConfig(t *testing.T) {
tmpDir, cleanup := testutil.TempDir(t)
defer cleanup()
// Set XDG_CONFIG_HOME to our temp directory
originalXDGConfig := os.Getenv("XDG_CONFIG_HOME")
_ = os.Setenv("XDG_CONFIG_HOME", tmpDir)
defer func() {
if originalXDGConfig != "" {
_ = os.Setenv("XDG_CONFIG_HOME", originalXDGConfig)
} else {
_ = os.Unsetenv("XDG_CONFIG_HOME")
}
}()
err := WriteDefaultConfig()
testutil.AssertNoError(t, err)
// Check that config file was created
configPath, _ := GetConfigPath()
t.Logf("Expected config path: %s", configPath)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
t.Errorf("config file was not created at: %s", configPath)
// List what files were actually created
if files, err := os.ReadDir(tmpDir); err == nil {
t.Logf("Files in tmpDir: %v", files)
}
}
// Verify config file content
config, err := InitConfig(configPath)
testutil.AssertNoError(t, err)
// Should have default values
testutil.AssertEqual(t, "default", config.Theme)
testutil.AssertEqual(t, "md", config.OutputFormat)
testutil.AssertEqual(t, ".", config.OutputDir)
}
func TestResolveThemeTemplate(t *testing.T) {
tests := []struct {
name string
theme string
expectError bool
shouldExist bool
expectedPath string
}{
{
name: "default theme",
theme: "default",
expectError: false,
shouldExist: true,
expectedPath: "templates/readme.tmpl",
},
{
name: "github theme",
theme: "github",
expectError: false,
shouldExist: true,
expectedPath: "templates/themes/github/readme.tmpl",
},
{
name: "gitlab theme",
theme: "gitlab",
expectError: false,
shouldExist: true,
expectedPath: "templates/themes/gitlab/readme.tmpl",
},
{
name: "minimal theme",
theme: "minimal",
expectError: false,
shouldExist: true,
expectedPath: "templates/themes/minimal/readme.tmpl",
},
{
name: "professional theme",
theme: "professional",
expectError: false,
shouldExist: true,
expectedPath: "templates/themes/professional/readme.tmpl",
},
{
name: "unknown theme",
theme: "nonexistent",
expectError: true,
},
{
name: "empty theme",
theme: "",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
path := resolveThemeTemplate(tt.theme)
if tt.expectError {
if path != "" {
t.Errorf("expected empty path on error, got: %s", path)
}
return
}
if path == "" {
t.Error("expected non-empty path")
}
if tt.expectedPath != "" {
testutil.AssertStringContains(t, path, tt.expectedPath)
}
// Note: We can't check file existence here because template files
// might not be present in the test environment
})
}
}
func TestConfigTokenHierarchy(t *testing.T) {
tests := []struct {
name string
setupFunc func(t *testing.T) func()
expectedToken string
}{
{
name: "GH_README_GITHUB_TOKEN has highest priority",
setupFunc: func(_ *testing.T) func() {
_ = os.Setenv("GH_README_GITHUB_TOKEN", "priority-token")
_ = os.Setenv("GITHUB_TOKEN", "fallback-token")
return func() {
_ = os.Unsetenv("GH_README_GITHUB_TOKEN")
_ = os.Unsetenv("GITHUB_TOKEN")
}
},
expectedToken: "priority-token",
},
{
name: "GITHUB_TOKEN as fallback",
setupFunc: func(_ *testing.T) func() {
_ = os.Unsetenv("GH_README_GITHUB_TOKEN")
_ = os.Setenv("GITHUB_TOKEN", "fallback-token")
return func() {
_ = os.Unsetenv("GITHUB_TOKEN")
}
},
expectedToken: "fallback-token",
},
{
name: "no environment variables",
setupFunc: func(_ *testing.T) func() {
_ = os.Unsetenv("GH_README_GITHUB_TOKEN")
_ = os.Unsetenv("GITHUB_TOKEN")
return func() {}
},
expectedToken: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cleanup := tt.setupFunc(t)
defer cleanup()
tmpDir, tmpCleanup := testutil.TempDir(t)
defer tmpCleanup()
// Use default config
config, err := LoadConfiguration("", tmpDir, tmpDir)
testutil.AssertNoError(t, err)
testutil.AssertEqual(t, tt.expectedToken, config.GitHubToken)
})
}
}
func TestConfigMerging(t *testing.T) {
tmpDir, cleanup := testutil.TempDir(t)
defer cleanup()
// Test config merging by creating config files and seeing the result
globalConfigDir := filepath.Join(tmpDir, ".config", "gh-action-readme")
_ = os.MkdirAll(globalConfigDir, 0755)
testutil.WriteTestFile(t, filepath.Join(globalConfigDir, "config.yaml"), `
theme: default
output_format: md
github_token: base-token
verbose: false
`)
repoRoot := filepath.Join(tmpDir, "repo")
_ = os.MkdirAll(repoRoot, 0755)
testutil.WriteTestFile(t, filepath.Join(repoRoot, ".ghreadme.yaml"), `
theme: github
output_format: html
verbose: true
`)
// Set HOME and XDG_CONFIG_HOME to temp directory
originalHome := os.Getenv("HOME")
originalXDGConfig := os.Getenv("XDG_CONFIG_HOME")
_ = os.Setenv("HOME", tmpDir)
_ = os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmpDir, ".config"))
defer func() {
if originalHome != "" {
_ = os.Setenv("HOME", originalHome)
} else {
_ = os.Unsetenv("HOME")
}
if originalXDGConfig != "" {
_ = os.Setenv("XDG_CONFIG_HOME", originalXDGConfig)
} else {
_ = os.Unsetenv("XDG_CONFIG_HOME")
}
}()
// Use the specific config file path instead of relying on XDG discovery
configPath := filepath.Join(tmpDir, ".config", "gh-action-readme", "config.yaml")
config, err := LoadConfiguration(configPath, repoRoot, repoRoot)
testutil.AssertNoError(t, err)
// Should have merged values
testutil.AssertEqual(t, "github", config.Theme) // from repo config
testutil.AssertEqual(t, "html", config.OutputFormat) // from repo config
testutil.AssertEqual(t, true, config.Verbose) // from repo config
testutil.AssertEqual(t, "base-token", config.GitHubToken) // from global config
testutil.AssertEqual(t, "schemas/schema.json", config.Schema) // default value
}