mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-01-26 11:14:04 +00:00
- 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
579 lines
15 KiB
Go
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
|
|
}
|