mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-03-13 02:00:18 +00:00
1442 lines
39 KiB
Go
1442 lines
39 KiB
Go
package internal
|
|
|
|
import (
|
|
"net/http"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/google/go-github/v74/github"
|
|
|
|
"github.com/ivuorinen/gh-action-readme/appconstants"
|
|
"github.com/ivuorinen/gh-action-readme/testutil"
|
|
)
|
|
|
|
func TestInitConfig(t *testing.T) {
|
|
|
|
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: testutil.TestThemeDefault,
|
|
OutputFormat: "md",
|
|
OutputDir: ".",
|
|
Template: testutil.TestTemplateWithPrefix,
|
|
Schema: "schemas/schema.json",
|
|
Verbose: false,
|
|
Quiet: false,
|
|
GitHubToken: "",
|
|
},
|
|
},
|
|
{
|
|
name: "custom config file",
|
|
configFile: testutil.TestFileCustomConfig,
|
|
setupFunc: func(t *testing.T, tempDir string) {
|
|
t.Helper()
|
|
testutil.WriteFileInDir(
|
|
t,
|
|
tempDir,
|
|
testutil.TestFileCustomConfig,
|
|
testutil.MustReadFixture(testutil.TestFixtureProfessionalConfig),
|
|
)
|
|
},
|
|
expected: &AppConfig{
|
|
Theme: testutil.TestThemeProfessional,
|
|
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: testutil.TestPathConfigYML,
|
|
setupFunc: func(t *testing.T, tempDir string) {
|
|
t.Helper()
|
|
testutil.WriteFileInDir(t, tempDir, testutil.TestPathConfigYML, "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.SetupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
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) {
|
|
t.Helper()
|
|
// Clear environment variables to ensure config file values are used
|
|
t.Setenv(appconstants.EnvGitHubTokenStandard, "")
|
|
t.Setenv(appconstants.EnvGitHubToken, "")
|
|
|
|
// Create global config
|
|
globalConfigDir := filepath.Join(tempDir, testutil.TestDirDotConfig, testutil.TestBinaryName)
|
|
globalConfigPath := WriteConfigFixture(t, globalConfigDir, testutil.TestConfigGlobalDefault)
|
|
|
|
// Create repo root with repo-specific config
|
|
repoRoot := filepath.Join(tempDir, "repo")
|
|
testutil.WriteFileInDir(t, repoRoot, testutil.TestFileGHReadmeYAML,
|
|
string(testutil.MustReadFixture(testutil.TestConfigRepoSimple)))
|
|
|
|
// Create current directory with action-specific config
|
|
currentDir := filepath.Join(repoRoot, "action")
|
|
WriteConfigFixture(t, currentDir, testutil.TestConfigActionSimple)
|
|
|
|
return globalConfigPath, repoRoot, currentDir
|
|
},
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
// Should have action-level overrides
|
|
testutil.AssertEqual(t, testutil.TestThemeProfessional, 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, testutil.TestTokenStd, config.GitHubToken)
|
|
},
|
|
},
|
|
{
|
|
name: "environment variable overrides",
|
|
setupFunc: func(t *testing.T, tempDir string) (string, string, string) {
|
|
t.Helper()
|
|
// Set environment variables
|
|
t.Setenv("GH_README_GITHUB_TOKEN", "env-token")
|
|
t.Setenv("GITHUB_TOKEN", "fallback-token")
|
|
|
|
// Create config file
|
|
testutil.WriteFileInDir(t, tempDir, testutil.TestPathConfigYML, `
|
|
theme: minimal
|
|
github_token: config-token
|
|
`)
|
|
configPath := filepath.Join(tempDir, testutil.TestPathConfigYML)
|
|
|
|
return configPath, tempDir, tempDir
|
|
},
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
// Environment variable should override config file
|
|
testutil.AssertEqual(t, "env-token", config.GitHubToken)
|
|
testutil.AssertEqual(t, testutil.TestThemeMinimal, config.Theme)
|
|
},
|
|
},
|
|
{
|
|
name: "XDG compliance",
|
|
setupFunc: func(t *testing.T, tempDir string) (string, string, string) {
|
|
t.Helper()
|
|
// Set XDG environment variables
|
|
xdgConfigHome := filepath.Join(tempDir, "xdg-config")
|
|
t.Setenv("XDG_CONFIG_HOME", xdgConfigHome)
|
|
|
|
// Create XDG-compliant config
|
|
configDir := filepath.Join(xdgConfigHome, testutil.TestBinaryName)
|
|
configPath := WriteConfigFixture(t, configDir, testutil.TestConfigGitHubVerbose)
|
|
|
|
return configPath, tempDir, tempDir
|
|
},
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
testutil.AssertEqual(t, testutil.TestThemeGitHub, config.Theme)
|
|
testutil.AssertEqual(t, true, config.Verbose)
|
|
},
|
|
},
|
|
{
|
|
name: "hidden config file discovery",
|
|
setupFunc: func(t *testing.T, tempDir string) (string, string, string) {
|
|
t.Helper()
|
|
repoRoot := filepath.Join(tempDir, "repo")
|
|
|
|
// Create multiple hidden config files
|
|
testutil.WriteFileInDir(t, repoRoot, testutil.TestFileGHReadmeYAML,
|
|
string(testutil.MustReadFixture(testutil.TestConfigMinimalTheme)))
|
|
|
|
configDir := filepath.Join(repoRoot, testutil.TestDirDotConfig)
|
|
testutil.WriteFileInDir(t, configDir, "ghreadme.yaml",
|
|
string(testutil.MustReadFixture(testutil.TestConfigProfessionalQuiet)))
|
|
|
|
githubDir := filepath.Join(repoRoot, ".github")
|
|
testutil.WriteFileInDir(t, githubDir, "ghreadme.yaml",
|
|
string(testutil.MustReadFixture(testutil.TestConfigGitHubVerbose)))
|
|
|
|
return "", repoRoot, repoRoot
|
|
},
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
// Should use the first found config (.ghreadme.yaml has priority)
|
|
testutil.AssertEqual(t, testutil.TestThemeMinimal, config.Theme)
|
|
testutil.AssertEqual(t, "json", config.OutputFormat)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tmpDir, cleanup := testutil.SetupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
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) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T, tempDir string)
|
|
contains string
|
|
}{
|
|
{
|
|
name: "XDG_CONFIG_HOME set",
|
|
setupFunc: func(t *testing.T, tempDir string) {
|
|
t.Helper()
|
|
t.Setenv("XDG_CONFIG_HOME", tempDir)
|
|
t.Setenv("HOME", "")
|
|
},
|
|
contains: testutil.TestBinaryName,
|
|
},
|
|
{
|
|
name: "HOME fallback",
|
|
setupFunc: func(t *testing.T, tempDir string) {
|
|
t.Helper()
|
|
t.Setenv("XDG_CONFIG_HOME", "")
|
|
t.Setenv("HOME", tempDir)
|
|
},
|
|
contains: testutil.TestDirDotConfig,
|
|
},
|
|
}
|
|
|
|
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) {
|
|
_, cleanup := testutil.SetupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
err := WriteDefaultConfig()
|
|
testutil.AssertNoError(t, err)
|
|
|
|
// Check that config file was created
|
|
configPath, _ := GetConfigPath()
|
|
t.Logf("Expected config path: %s", configPath)
|
|
testutil.AssertFileExists(t, configPath)
|
|
|
|
// Verify config file content
|
|
config, err := InitConfig(configPath)
|
|
testutil.AssertNoError(t, err)
|
|
|
|
// Should have default values
|
|
testutil.AssertEqual(t, testutil.TestThemeDefault, config.Theme)
|
|
testutil.AssertEqual(t, "md", config.OutputFormat)
|
|
testutil.AssertEqual(t, ".", config.OutputDir)
|
|
}
|
|
|
|
func TestResolveThemeTemplate(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
theme string
|
|
expectError bool
|
|
shouldExist bool
|
|
expectedPath string
|
|
}{
|
|
{
|
|
name: "default theme",
|
|
theme: testutil.TestThemeDefault,
|
|
expectError: false,
|
|
shouldExist: true,
|
|
expectedPath: testutil.TestTemplateWithPrefix,
|
|
},
|
|
{
|
|
name: "github theme",
|
|
theme: testutil.TestThemeGitHub,
|
|
expectError: false,
|
|
shouldExist: true,
|
|
expectedPath: "templates/themes/github/readme.tmpl",
|
|
},
|
|
{
|
|
name: "gitlab theme",
|
|
theme: testutil.TestThemeGitLab,
|
|
expectError: false,
|
|
shouldExist: true,
|
|
expectedPath: "templates/themes/gitlab/readme.tmpl",
|
|
},
|
|
{
|
|
name: "minimal theme",
|
|
theme: testutil.TestThemeMinimal,
|
|
expectError: false,
|
|
shouldExist: true,
|
|
expectedPath: "templates/themes/minimal/readme.tmpl",
|
|
},
|
|
{
|
|
name: "professional theme",
|
|
theme: testutil.TestThemeProfessional,
|
|
expectError: false,
|
|
shouldExist: true,
|
|
expectedPath: "templates/themes/professional/readme.tmpl",
|
|
},
|
|
{
|
|
name: testutil.TestCaseNameUnknownTheme,
|
|
theme: "nonexistent",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: testutil.TestCaseNameEmptyTheme,
|
|
theme: "",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
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 := testutil.GetGitHubTokenHierarchyTests()
|
|
|
|
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, testutil.TestDirDotConfig, testutil.TestBinaryName)
|
|
WriteConfigFixture(t, globalConfigDir, testutil.TestConfigGlobalBaseToken)
|
|
|
|
repoRoot := filepath.Join(tmpDir, "repo")
|
|
testutil.WriteFileInDir(t, repoRoot, testutil.TestFileGHReadmeYAML,
|
|
string(testutil.MustReadFixture(testutil.TestConfigRepoVerbose)))
|
|
|
|
// Set HOME and XDG_CONFIG_HOME to temp directory
|
|
testutil.SetupConfigEnvironment(t, tmpDir)
|
|
|
|
// Use the specific config file path instead of relying on XDG discovery
|
|
configPath := filepath.Join(
|
|
tmpDir,
|
|
testutil.TestDirDotConfig,
|
|
testutil.TestBinaryName,
|
|
testutil.TestFileConfigYAML,
|
|
)
|
|
config, err := LoadConfiguration(configPath, repoRoot, repoRoot)
|
|
testutil.AssertNoError(t, err)
|
|
|
|
// Should have merged values
|
|
testutil.AssertEqual(t, testutil.TestThemeGitHub, 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
|
|
}
|
|
|
|
// TestGetGitHubToken tests GitHub token resolution with different priority levels.
|
|
func TestGetGitHubToken(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
toolEnvToken string
|
|
stdEnvToken string
|
|
configToken string
|
|
expectedToken string
|
|
}{
|
|
{
|
|
name: "tool-specific env var has highest priority",
|
|
toolEnvToken: "tool-token",
|
|
stdEnvToken: testutil.TestTokenStd,
|
|
configToken: testutil.TestTokenConfig,
|
|
expectedToken: "tool-token",
|
|
},
|
|
{
|
|
name: "standard env var when tool env not set",
|
|
toolEnvToken: "",
|
|
stdEnvToken: testutil.TestTokenStd,
|
|
configToken: testutil.TestTokenConfig,
|
|
expectedToken: testutil.TestTokenStd,
|
|
},
|
|
{
|
|
name: "config token when env vars not set",
|
|
toolEnvToken: "",
|
|
stdEnvToken: "",
|
|
configToken: testutil.TestTokenConfig,
|
|
expectedToken: testutil.TestTokenConfig,
|
|
},
|
|
{
|
|
name: "empty string when nothing set",
|
|
toolEnvToken: "",
|
|
stdEnvToken: "",
|
|
configToken: "",
|
|
expectedToken: "",
|
|
},
|
|
{
|
|
name: "empty env var does not override config",
|
|
toolEnvToken: "",
|
|
stdEnvToken: "",
|
|
configToken: testutil.TestTokenConfig,
|
|
expectedToken: testutil.TestTokenConfig,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Set up environment
|
|
if tt.toolEnvToken != "" {
|
|
t.Setenv(appconstants.EnvGitHubToken, tt.toolEnvToken)
|
|
} else {
|
|
t.Setenv(appconstants.EnvGitHubToken, "")
|
|
}
|
|
if tt.stdEnvToken != "" {
|
|
t.Setenv(appconstants.EnvGitHubTokenStandard, tt.stdEnvToken)
|
|
} else {
|
|
t.Setenv(appconstants.EnvGitHubTokenStandard, "")
|
|
}
|
|
|
|
config := &AppConfig{GitHubToken: tt.configToken}
|
|
result := GetGitHubToken(config)
|
|
|
|
testutil.AssertEqual(t, tt.expectedToken, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMergeMapFields tests the merging of map fields in configuration.
|
|
func TestMergeMapFields(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
dst *AppConfig
|
|
src *AppConfig
|
|
expected *AppConfig
|
|
}{
|
|
createMapMergeTest(
|
|
"merge permissions into empty dst",
|
|
nil,
|
|
map[string]string{"read": "read", "write": "write"},
|
|
map[string]string{"read": "read", "write": "write"},
|
|
true, // isPermissions
|
|
),
|
|
createMapMergeTest(
|
|
"merge permissions into existing dst",
|
|
map[string]string{"read": "existing"},
|
|
map[string]string{"read": "new", "write": "write"},
|
|
map[string]string{"read": "new", "write": "write"},
|
|
true, // isPermissions
|
|
),
|
|
createMapMergeTest(
|
|
"merge variables into empty dst",
|
|
nil,
|
|
map[string]string{"VAR1": "value1", "VAR2": "value2"},
|
|
map[string]string{"VAR1": "value1", "VAR2": "value2"},
|
|
false, // isPermissions
|
|
),
|
|
createMapMergeTest(
|
|
"merge variables into existing dst",
|
|
map[string]string{"VAR1": "existing"},
|
|
map[string]string{"VAR1": "new", "VAR2": "value2"},
|
|
map[string]string{"VAR1": "new", "VAR2": "value2"},
|
|
false, // isPermissions
|
|
),
|
|
{
|
|
name: "merge both permissions and variables",
|
|
dst: &AppConfig{
|
|
Permissions: map[string]string{"read": "existing"},
|
|
},
|
|
src: &AppConfig{
|
|
Permissions: map[string]string{"write": "write"},
|
|
Variables: map[string]string{"VAR1": "value1"},
|
|
},
|
|
expected: &AppConfig{
|
|
Permissions: map[string]string{"read": "existing", "write": "write"},
|
|
Variables: map[string]string{"VAR1": "value1"},
|
|
},
|
|
},
|
|
{
|
|
name: "empty src does not affect dst",
|
|
dst: &AppConfig{
|
|
Permissions: map[string]string{"read": "read"},
|
|
Variables: map[string]string{"VAR1": "value1"},
|
|
},
|
|
src: &AppConfig{},
|
|
expected: &AppConfig{
|
|
Permissions: map[string]string{"read": "read"},
|
|
Variables: map[string]string{"VAR1": "value1"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Deep copy dst to avoid modifying test data
|
|
dst := &AppConfig{}
|
|
if tt.dst.Permissions != nil {
|
|
dst.Permissions = make(map[string]string)
|
|
for k, v := range tt.dst.Permissions {
|
|
dst.Permissions[k] = v
|
|
}
|
|
}
|
|
if tt.dst.Variables != nil {
|
|
dst.Variables = make(map[string]string)
|
|
for k, v := range tt.dst.Variables {
|
|
dst.Variables[k] = v
|
|
}
|
|
}
|
|
|
|
mergeMapFields(dst, tt.src)
|
|
|
|
testutil.AssertEqual(t, tt.expected.Permissions, dst.Permissions)
|
|
testutil.AssertEqual(t, tt.expected.Variables, dst.Variables)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMergeSliceFields tests the merging of slice fields in configuration.
|
|
func TestMergeSliceFields(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
dst *AppConfig
|
|
src *AppConfig
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "merge runsOn into empty dst",
|
|
dst: &AppConfig{},
|
|
src: &AppConfig{RunsOn: []string{testutil.RunnerUbuntuLatest, testutil.RunnerWindowsLatest}},
|
|
expected: []string{testutil.RunnerUbuntuLatest, testutil.RunnerWindowsLatest},
|
|
},
|
|
{
|
|
name: "merge runsOn replaces existing dst",
|
|
dst: &AppConfig{RunsOn: []string{"macos-latest"}},
|
|
src: &AppConfig{RunsOn: []string{testutil.RunnerUbuntuLatest, testutil.RunnerWindowsLatest}},
|
|
expected: []string{testutil.RunnerUbuntuLatest, testutil.RunnerWindowsLatest},
|
|
},
|
|
{
|
|
name: "empty src does not affect dst",
|
|
dst: &AppConfig{RunsOn: []string{testutil.RunnerUbuntuLatest}},
|
|
src: &AppConfig{},
|
|
expected: []string{testutil.RunnerUbuntuLatest},
|
|
},
|
|
{
|
|
name: "empty src slice does not affect dst",
|
|
dst: &AppConfig{RunsOn: []string{testutil.RunnerUbuntuLatest}},
|
|
src: &AppConfig{RunsOn: []string{}},
|
|
expected: []string{testutil.RunnerUbuntuLatest},
|
|
},
|
|
{
|
|
name: "single item slice",
|
|
dst: &AppConfig{},
|
|
src: &AppConfig{RunsOn: []string{"self-hosted"}},
|
|
expected: []string{"self-hosted"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
mergeSliceFields(tt.dst, tt.src)
|
|
|
|
// Compare slices manually since they can't be compared directly
|
|
if len(tt.expected) != len(tt.dst.RunsOn) {
|
|
t.Errorf("expected slice length %d, got %d", len(tt.expected), len(tt.dst.RunsOn))
|
|
|
|
return
|
|
}
|
|
for i, expected := range tt.expected {
|
|
if i >= len(tt.dst.RunsOn) || tt.dst.RunsOn[i] != expected {
|
|
t.Errorf("expected %v, got %v", tt.expected, tt.dst.RunsOn)
|
|
|
|
return
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// assertBooleanConfigFields is a helper that checks all boolean fields in AppConfig.
|
|
func assertBooleanConfigFields(t *testing.T, got, want *AppConfig) {
|
|
t.Helper()
|
|
|
|
fields := []struct {
|
|
name string
|
|
gotVal bool
|
|
wantVal bool
|
|
}{
|
|
{"AnalyzeDependencies", got.AnalyzeDependencies, want.AnalyzeDependencies},
|
|
{"ShowSecurityInfo", got.ShowSecurityInfo, want.ShowSecurityInfo},
|
|
{"Verbose", got.Verbose, want.Verbose},
|
|
{"Quiet", got.Quiet, want.Quiet},
|
|
{"UseDefaultBranch", got.UseDefaultBranch, want.UseDefaultBranch},
|
|
}
|
|
|
|
for _, field := range fields {
|
|
if field.gotVal != field.wantVal {
|
|
t.Errorf("%s = %v, want %v", field.name, field.gotVal, field.wantVal)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMergeBooleanFields tests merging boolean configuration fields.
|
|
func TestMergeBooleanFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
dst *AppConfig
|
|
src *AppConfig
|
|
want *AppConfig
|
|
}{
|
|
createBoolFieldMergeTest(
|
|
"merge all true values",
|
|
boolFields{false, false, false, false, false},
|
|
boolFields{true, true, true, true, true},
|
|
boolFields{true, true, true, true, true},
|
|
),
|
|
createBoolFieldMergeTest(
|
|
"merge only some true values",
|
|
boolFields{false, true, false, true, false},
|
|
boolFields{true, false, true, false, false},
|
|
boolFields{true, true, true, true, false},
|
|
),
|
|
createBoolFieldMergeTest(
|
|
"merge with all source false",
|
|
boolFields{true, true, true, true, true},
|
|
boolFields{false, false, false, false, false},
|
|
boolFields{true, true, true, true, true},
|
|
),
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mergeBooleanFields(tt.dst, tt.src)
|
|
|
|
assertBooleanConfigFields(t, tt.dst, tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMergeSecurityFields tests merging security-sensitive configuration fields.
|
|
func TestMergeSecurityFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
dst *AppConfig
|
|
src *AppConfig
|
|
allowTokens bool
|
|
want *AppConfig
|
|
}{
|
|
createTokenMergeTest(
|
|
"allow tokens - merge token",
|
|
"",
|
|
"ghp_test_token",
|
|
"ghp_test_token",
|
|
true,
|
|
),
|
|
createTokenMergeTest(
|
|
"disallow tokens - do not merge token",
|
|
"",
|
|
"ghp_test_token",
|
|
"",
|
|
false,
|
|
),
|
|
createTokenMergeTest(
|
|
"allow tokens - do not overwrite with empty",
|
|
"ghp_existing_token",
|
|
"",
|
|
"ghp_existing_token",
|
|
true,
|
|
),
|
|
createTokenMergeTest(
|
|
"allow tokens - overwrite existing token",
|
|
"ghp_old_token",
|
|
"ghp_new_token",
|
|
"ghp_new_token",
|
|
true,
|
|
),
|
|
{
|
|
name: "allow tokens - merge repo overrides into nil dst",
|
|
dst: &AppConfig{
|
|
RepoOverrides: nil,
|
|
},
|
|
src: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.OrgRepo: {Organization: testutil.OrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
allowTokens: true,
|
|
want: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.OrgRepo: {Organization: testutil.OrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "allow tokens - merge repo overrides into existing dst",
|
|
dst: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.ExistingRepo: {Organization: testutil.ExistingOrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
src: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.NewRepo: {Organization: testutil.NewOrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
allowTokens: true,
|
|
want: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.ExistingRepo: {Organization: testutil.ExistingOrgName, Repository: testutil.RepoName},
|
|
testutil.NewRepo: {Organization: testutil.NewOrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "disallow tokens - do not merge repo overrides",
|
|
dst: &AppConfig{
|
|
RepoOverrides: nil,
|
|
},
|
|
src: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.OrgRepo: {Organization: testutil.OrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
allowTokens: false,
|
|
want: &AppConfig{
|
|
RepoOverrides: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "allow tokens - empty source repo overrides",
|
|
dst: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.ExistingRepo: {Organization: testutil.ExistingOrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
src: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{},
|
|
},
|
|
allowTokens: true,
|
|
want: &AppConfig{
|
|
RepoOverrides: map[string]AppConfig{
|
|
testutil.ExistingRepo: {Organization: testutil.ExistingOrgName, Repository: testutil.RepoName},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mergeSecurityFields(tt.dst, tt.src, tt.allowTokens)
|
|
|
|
if tt.dst.GitHubToken != tt.want.GitHubToken {
|
|
t.Errorf("GitHubToken = %q, want %q",
|
|
tt.dst.GitHubToken, tt.want.GitHubToken)
|
|
}
|
|
|
|
assertRepoOverrides(t, tt.dst.RepoOverrides, tt.want.RepoOverrides)
|
|
})
|
|
}
|
|
}
|
|
|
|
// assertRepoOverrides validates that RepoOverrides match expectations.
|
|
func assertRepoOverrides(t *testing.T, got, want map[string]AppConfig) {
|
|
t.Helper()
|
|
|
|
if want == nil {
|
|
if got != nil {
|
|
t.Errorf("RepoOverrides = %v, want nil", got)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if got == nil {
|
|
t.Error("RepoOverrides is nil, want non-nil")
|
|
|
|
return
|
|
}
|
|
|
|
for key, wantVal := range want {
|
|
gotVal, exists := got[key]
|
|
if !exists {
|
|
t.Errorf("RepoOverrides missing key %q", key)
|
|
} else if gotVal.Organization != wantVal.Organization ||
|
|
gotVal.Repository != wantVal.Repository {
|
|
t.Errorf("RepoOverrides[%q] = %+v, want %+v",
|
|
key, gotVal, wantVal)
|
|
}
|
|
}
|
|
|
|
if len(got) != len(want) {
|
|
t.Errorf("RepoOverrides length = %d, want %d", len(got), len(want))
|
|
}
|
|
}
|
|
|
|
// assertGitHubClientValid checks that a GitHub client is properly initialized.
|
|
func assertGitHubClientValid(t *testing.T, client *GitHubClient, expectedToken string) {
|
|
t.Helper()
|
|
if client == nil {
|
|
t.Error("expected non-nil client")
|
|
|
|
return
|
|
}
|
|
if client.Client == nil {
|
|
t.Error("expected non-nil GitHub client")
|
|
}
|
|
if client.Token != expectedToken {
|
|
t.Errorf("expected token %q, got %q", expectedToken, client.Token)
|
|
}
|
|
}
|
|
|
|
// TestNewGitHubClient_EdgeCases tests GitHub client initialization edge cases.
|
|
func TestNewGitHubClientEdgeCases(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
token string
|
|
expectError bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "valid classic GitHub token",
|
|
token: "ghp_1234567890abcdefghijklmnopqrstuvwxyzABCD",
|
|
expectError: false,
|
|
description: "Should create client with valid classic token",
|
|
},
|
|
{ // #nosec G101 -- test token, not a real credential
|
|
name: "valid fine-grained PAT",
|
|
token: "github_pat_11AAAAAA0AAAAaAaaAaaaAaa_AaAAaAAaAAAaAAAAAaAAaAAaAaAAaAAAAaAAAAAAAAaAAaAAaAaaAA",
|
|
expectError: false,
|
|
description: "Should create client with fine-grained token",
|
|
},
|
|
{
|
|
name: "empty token",
|
|
token: "",
|
|
expectError: false,
|
|
description: "Should create client without authentication",
|
|
},
|
|
{
|
|
name: "short token",
|
|
token: "ghp_short",
|
|
expectError: false,
|
|
description: "Should create client even with unusual token format",
|
|
},
|
|
{
|
|
name: "token with special characters",
|
|
token: "test-token_123",
|
|
expectError: false,
|
|
description: "Should handle tokens with various characters",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, err := NewGitHubClient(tt.token)
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
testutil.AssertNoError(t, err)
|
|
assertGitHubClientValid(t, client, tt.token)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateGitHubClientCreation tests raw GitHub client creation validation.
|
|
// This test demonstrates the use of the assertGitHubClient helper for
|
|
// validating github.Client instances with different configurations.
|
|
func TestValidateGitHubClientCreation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T) (*github.Client, error)
|
|
expectError bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "successful client creation with nil transport",
|
|
setupFunc: func(t *testing.T) (*github.Client, error) {
|
|
t.Helper()
|
|
// Valid client creation - github.NewClient handles nil gracefully
|
|
return github.NewClient(nil), nil
|
|
},
|
|
expectError: false,
|
|
description: "Should create valid GitHub client with default transport",
|
|
},
|
|
{
|
|
name: "successful client creation with custom HTTP client",
|
|
setupFunc: func(t *testing.T) (*github.Client, error) {
|
|
t.Helper()
|
|
// Create client with custom HTTP client for testing
|
|
mockHTTPClient := &http.Client{
|
|
Transport: &testutil.MockTransport{},
|
|
}
|
|
|
|
return github.NewClient(mockHTTPClient), nil
|
|
},
|
|
expectError: false,
|
|
description: "Should create valid GitHub client with custom transport",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, err := tt.setupFunc(t)
|
|
|
|
// Use the assertGitHubClient helper to validate the result
|
|
assertGitHubClient(t, client, err, tt.expectError)
|
|
})
|
|
}
|
|
}
|
|
|
|
// runTemplatePathTest runs a template path test with setup and validation.
|
|
func runTemplatePathTest(
|
|
t *testing.T,
|
|
setupFunc func(*testing.T) (string, func()),
|
|
checkFunc func(*testing.T, string),
|
|
) {
|
|
t.Helper()
|
|
templatePath, cleanup := setupFunc(t)
|
|
defer cleanup()
|
|
result := resolveTemplatePath(templatePath)
|
|
if checkFunc != nil {
|
|
checkFunc(t, result)
|
|
}
|
|
}
|
|
|
|
// TestResolveTemplatePath_EdgeCases tests template path resolution edge cases.
|
|
func TestResolveTemplatePathEdgeCases(t *testing.T) {
|
|
// Note: Cannot use t.Parallel() because one subtest uses t.Chdir()
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T) (templatePath string, cleanup func())
|
|
checkFunc func(t *testing.T, result string)
|
|
description string
|
|
}{
|
|
{
|
|
name: "absolute path - return as-is",
|
|
setupFunc: func(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
testutil.WriteFileInDir(t, tmpDir, "template.tmpl", "test template")
|
|
absPath := filepath.Join(tmpDir, "template.tmpl")
|
|
|
|
return absPath, cleanup
|
|
},
|
|
checkFunc: func(t *testing.T, result string) {
|
|
t.Helper()
|
|
if !filepath.IsAbs(result) {
|
|
t.Errorf("expected absolute path, got: %s", result)
|
|
}
|
|
},
|
|
description: "Absolute paths should be returned unchanged",
|
|
},
|
|
{
|
|
name: "embedded template - available",
|
|
setupFunc: func(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
// Use a path we know is embedded
|
|
return testutil.TestTemplateReadme, func() { /* No cleanup needed for embedded templates */ }
|
|
},
|
|
checkFunc: func(t *testing.T, result string) {
|
|
t.Helper()
|
|
if result != testutil.TestTemplateReadme {
|
|
t.Errorf("expected %q, got: %s", testutil.TestTemplateReadme, result)
|
|
}
|
|
},
|
|
description: "Embedded templates should return original path",
|
|
},
|
|
{
|
|
name: "embedded template with templates/ prefix",
|
|
setupFunc: func(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
|
|
return testutil.TestTemplateWithPrefix, func() { /* No cleanup needed for embedded templates */ }
|
|
},
|
|
checkFunc: func(t *testing.T, result string) {
|
|
t.Helper()
|
|
if result != testutil.TestTemplateWithPrefix {
|
|
t.Errorf("expected %q, got: %s", testutil.TestTemplateWithPrefix, result)
|
|
}
|
|
},
|
|
description: "Embedded templates with prefix should return original path",
|
|
},
|
|
{
|
|
name: "filesystem template - exists in current dir",
|
|
setupFunc: func(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
// Create template in current directory
|
|
templateName := "custom-template.tmpl"
|
|
testutil.WriteFileInDir(t, tmpDir, templateName, "custom template")
|
|
|
|
// Change to tmpDir
|
|
t.Chdir(tmpDir)
|
|
|
|
return templateName, cleanup
|
|
},
|
|
checkFunc: func(t *testing.T, result string) {
|
|
t.Helper()
|
|
if result == "" {
|
|
t.Error(testutil.TestMsgExpectedNonEmpty)
|
|
}
|
|
},
|
|
description: "Templates in current directory should be found",
|
|
},
|
|
{
|
|
name: "non-existent template - fallback to original path",
|
|
setupFunc: func(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
|
|
return "nonexistent-template.tmpl", func() { /* No cleanup needed for non-existent template test */ }
|
|
},
|
|
checkFunc: func(t *testing.T, result string) {
|
|
t.Helper()
|
|
if result != "nonexistent-template.tmpl" {
|
|
t.Errorf("expected original path, got: %s", result)
|
|
}
|
|
},
|
|
description: "Non-existent templates should return original path",
|
|
},
|
|
{
|
|
name: testutil.TestCaseNameEmptyPath,
|
|
setupFunc: func(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
|
|
return "", func() { /* No cleanup needed for empty path test */ }
|
|
},
|
|
checkFunc: func(t *testing.T, _ string) {
|
|
t.Helper()
|
|
// Empty path may return binary directory or empty string
|
|
// depending on whether GetBinaryDir succeeds
|
|
// Just verify it doesn't crash
|
|
},
|
|
description: "Empty path should not crash",
|
|
},
|
|
{
|
|
name: "relative path with subdirectory",
|
|
setupFunc: func(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
|
|
return "themes/github/readme.tmpl", func() { /* No cleanup needed for relative path test */ }
|
|
},
|
|
checkFunc: func(t *testing.T, result string) {
|
|
t.Helper()
|
|
// Should return the path (either embedded or fallback)
|
|
if result == "" {
|
|
t.Error(testutil.TestMsgExpectedNonEmpty)
|
|
}
|
|
},
|
|
description: "Relative paths with subdirectories should be resolved",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Note: Cannot use t.Parallel() because one subtest uses t.Chdir()
|
|
runTemplatePathTest(t, tt.setupFunc, tt.checkFunc)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDetectRepositoryName_EdgeCases tests repository name detection edge cases.
|
|
func TestDetectRepositoryNameEdgeCases(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T) string
|
|
expectedResult string
|
|
description string
|
|
}{
|
|
{
|
|
name: "empty repo root",
|
|
setupFunc: func(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
return ""
|
|
},
|
|
expectedResult: "",
|
|
description: "Empty repo root should return empty string",
|
|
},
|
|
{
|
|
name: "non-existent directory",
|
|
setupFunc: func(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
return "/nonexistent/path/to/repo"
|
|
},
|
|
expectedResult: "",
|
|
description: "Non-existent directory should return empty string",
|
|
},
|
|
{
|
|
name: "directory without git",
|
|
setupFunc: func(t *testing.T) string {
|
|
t.Helper()
|
|
tmpDir, _ := testutil.TempDir(t)
|
|
|
|
return tmpDir
|
|
},
|
|
expectedResult: "",
|
|
description: "Directory without .git should return empty string",
|
|
},
|
|
createGitRemoteTestCase(
|
|
"valid git repository with GitHub remote",
|
|
`[remote "origin"]
|
|
url = https://github.com/testorg/testrepo.git
|
|
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
`,
|
|
"testorg/testrepo",
|
|
"Valid GitHub repo should return org/repo",
|
|
),
|
|
createGitRemoteTestCase(
|
|
"git repository with SSH remote",
|
|
`[remote "origin"]
|
|
url = git@github.com:sshorg/sshrepo.git
|
|
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
`,
|
|
"sshorg/sshrepo",
|
|
"SSH remote should be parsed correctly",
|
|
),
|
|
createGitRemoteTestCase(
|
|
"git repository without remote",
|
|
"", // No config content
|
|
"",
|
|
"Repository without remote should return empty string",
|
|
),
|
|
createGitRemoteTestCase(
|
|
"git repository with non-GitHub remote",
|
|
`[remote "origin"]
|
|
url = https://gitlab.com/glorg/glrepo.git
|
|
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
`,
|
|
"",
|
|
"Non-GitHub remote should return empty string",
|
|
),
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoRoot := tt.setupFunc(t)
|
|
result := DetectRepositoryName(repoRoot)
|
|
|
|
if result != tt.expectedResult {
|
|
t.Errorf("DetectRepositoryName() = %q, want %q (test: %s)",
|
|
result, tt.expectedResult, tt.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLoadConfiguration_EdgeCases tests configuration loading edge cases.
|
|
func TestLoadConfigurationEdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T) (configFile, repoRoot, currentDir string)
|
|
expectError bool
|
|
checkFunc func(t *testing.T, config *AppConfig)
|
|
description string
|
|
}{
|
|
{
|
|
name: "empty config file path with defaults",
|
|
setupFunc: func(t *testing.T) (string, string, string) {
|
|
t.Helper()
|
|
tmpDir, _ := testutil.TempDir(t)
|
|
testutil.SetupConfigEnvironment(t, tmpDir)
|
|
|
|
return "", tmpDir, tmpDir
|
|
},
|
|
expectError: false,
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
if config == nil {
|
|
t.Fatal(testutil.TestMsgExpectedNonNilConfig)
|
|
}
|
|
// Should have default values
|
|
if config.Theme == "" {
|
|
t.Error("expected non-empty theme (default)")
|
|
}
|
|
},
|
|
description: "Empty config file should load defaults",
|
|
},
|
|
{
|
|
name: "all paths empty",
|
|
setupFunc: func(t *testing.T) (string, string, string) {
|
|
t.Helper()
|
|
tmpDir, _ := testutil.TempDir(t)
|
|
t.Setenv("HOME", tmpDir)
|
|
|
|
return "", "", ""
|
|
},
|
|
expectError: false,
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
if config == nil {
|
|
t.Fatal(testutil.TestMsgExpectedNonNilConfig)
|
|
}
|
|
},
|
|
description: "All empty paths should still return config",
|
|
},
|
|
{
|
|
name: "config file with minimal values",
|
|
setupFunc: func(t *testing.T) (string, string, string) {
|
|
t.Helper()
|
|
tmpDir, _ := testutil.TempDir(t)
|
|
testutil.WriteFileInDir(t, tmpDir, testutil.TestFileConfigYAML, "theme: minimal\n")
|
|
configPath := filepath.Join(tmpDir, testutil.TestFileConfigYAML)
|
|
|
|
return configPath, tmpDir, tmpDir
|
|
},
|
|
expectError: false,
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
testutil.AssertEqual(t, testutil.TestThemeMinimal, config.Theme)
|
|
},
|
|
description: "Minimal config should merge with defaults",
|
|
},
|
|
{
|
|
name: "invalid config file path",
|
|
setupFunc: func(t *testing.T) (string, string, string) {
|
|
t.Helper()
|
|
tmpDir, _ := testutil.TempDir(t)
|
|
|
|
return filepath.Join(tmpDir, "nonexistent.yaml"), tmpDir, tmpDir
|
|
},
|
|
expectError: true,
|
|
description: "Invalid config file path should error",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
configFile, repoRoot, currentDir := tt.setupFunc(t)
|
|
|
|
config, err := LoadConfiguration(configFile, repoRoot, currentDir)
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
} else {
|
|
testutil.AssertNoError(t, err)
|
|
|
|
if tt.checkFunc != nil {
|
|
tt.checkFunc(t, config)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestInitConfig_EdgeCases tests config initialization edge cases.
|
|
func TestInitConfigEdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T) string
|
|
expectError bool
|
|
checkFunc func(t *testing.T, config *AppConfig)
|
|
description string
|
|
}{
|
|
{
|
|
name: "empty config file path - use default",
|
|
setupFunc: func(t *testing.T) string {
|
|
t.Helper()
|
|
tmpDir, _ := testutil.TempDir(t)
|
|
testutil.SetupConfigEnvironment(t, tmpDir)
|
|
|
|
return ""
|
|
},
|
|
expectError: false,
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
if config == nil {
|
|
t.Fatal(testutil.TestMsgExpectedNonNilConfig)
|
|
}
|
|
// Should have default values
|
|
testutil.AssertEqual(t, testutil.TestThemeDefault, config.Theme)
|
|
},
|
|
description: "Empty path should use default config",
|
|
},
|
|
{
|
|
name: "config file with empty values",
|
|
setupFunc: func(t *testing.T) string {
|
|
t.Helper()
|
|
tmpDir, _ := testutil.TempDir(t)
|
|
testutil.WriteFileInDir(t, tmpDir, "empty.yaml", "---\n")
|
|
configPath := filepath.Join(tmpDir, "empty.yaml")
|
|
|
|
return configPath
|
|
},
|
|
expectError: false,
|
|
checkFunc: func(t *testing.T, config *AppConfig) {
|
|
t.Helper()
|
|
// Should still have default values filled in
|
|
if config.Theme == "" {
|
|
t.Error("expected non-empty theme from defaults")
|
|
}
|
|
},
|
|
description: "Empty config should be filled with defaults",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
configPath := tt.setupFunc(t)
|
|
|
|
config, err := InitConfig(configPath)
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
} else {
|
|
testutil.AssertNoError(t, err)
|
|
|
|
if tt.checkFunc != nil {
|
|
tt.checkFunc(t, config)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|