mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-02-20 13:51:47 +00:00
feat: go 1.25.5, dependency updates, renamed internal/errors (#129)
* feat: rename internal/errors to internal/apperrors * fix(tests): clear env values before using in tests * feat: rename internal/errors to internal/apperrors * chore(deps): update go and all dependencies * chore: remove renovate from pre-commit, formatting * chore: sonarcloud fixes * feat: consolidate constants to appconstants/constants.go * chore: sonarcloud fixes * feat: simplification, deduplication, test utils * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: sonarcloud fixes * chore: clean up * fix: config discovery, const deduplication * chore: fixes
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
|
||||
"github.com/ivuorinen/gh-action-readme/appconstants"
|
||||
)
|
||||
|
||||
// fixtureCache provides thread-safe caching of fixture content.
|
||||
@@ -48,12 +50,12 @@ func mustReadFixture(filename string) string {
|
||||
// Load from disk
|
||||
_, currentFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
panic("failed to get current file path")
|
||||
panic(appconstants.ErrFailedToGetCurrentFilePath)
|
||||
}
|
||||
|
||||
// Get the project root (go up from testutil/fixtures.go to project root)
|
||||
projectRoot := filepath.Dir(filepath.Dir(currentFile))
|
||||
fixturePath := filepath.Join(projectRoot, "testdata", "yaml-fixtures", filename)
|
||||
fixturePath := filepath.Join(projectRoot, appconstants.DirTestdata, appconstants.DirYAMLFixtures, filename)
|
||||
|
||||
contentBytes, err := os.ReadFile(fixturePath) // #nosec G304 -- test fixture path from project structure
|
||||
if err != nil {
|
||||
@@ -68,28 +70,20 @@ func mustReadFixture(filename string) string {
|
||||
return content
|
||||
}
|
||||
|
||||
// Constants for fixture management.
|
||||
const (
|
||||
// YmlExtension represents the standard YAML file extension.
|
||||
YmlExtension = ".yml"
|
||||
// YamlExtension represents the alternative YAML file extension.
|
||||
YamlExtension = ".yaml"
|
||||
)
|
||||
|
||||
// ActionType represents the type of GitHub Action being tested.
|
||||
type ActionType string
|
||||
|
||||
const (
|
||||
// ActionTypeJavaScript represents JavaScript-based GitHub Actions that run on Node.js.
|
||||
ActionTypeJavaScript ActionType = "javascript"
|
||||
ActionTypeJavaScript ActionType = ActionType(appconstants.ActionTypeJavaScript)
|
||||
// ActionTypeComposite represents composite GitHub Actions that combine multiple steps.
|
||||
ActionTypeComposite ActionType = "composite"
|
||||
ActionTypeComposite ActionType = ActionType(appconstants.ActionTypeComposite)
|
||||
// ActionTypeDocker represents Docker-based GitHub Actions that run in containers.
|
||||
ActionTypeDocker ActionType = "docker"
|
||||
ActionTypeDocker ActionType = ActionType(appconstants.ActionTypeDocker)
|
||||
// ActionTypeInvalid represents invalid or malformed GitHub Actions for testing error scenarios.
|
||||
ActionTypeInvalid ActionType = "invalid"
|
||||
ActionTypeInvalid ActionType = ActionType(appconstants.ActionTypeInvalid)
|
||||
// ActionTypeMinimal represents minimal GitHub Actions with basic configuration.
|
||||
ActionTypeMinimal ActionType = "minimal"
|
||||
ActionTypeMinimal ActionType = ActionType(appconstants.ActionTypeMinimal)
|
||||
)
|
||||
|
||||
// TestScenario represents a structured test scenario with metadata.
|
||||
@@ -338,11 +332,11 @@ var PackageJSONContent = func() string {
|
||||
result += " \"scripts\": {\n"
|
||||
result += " \"test\": \"jest\",\n"
|
||||
result += " \"build\": \"webpack\"\n"
|
||||
result += " },\n"
|
||||
result += appconstants.JSONCloseBrace
|
||||
result += " \"dependencies\": {\n"
|
||||
result += " \"@actions/core\": \"^1.10.0\",\n"
|
||||
result += " \"@actions/github\": \"^5.1.1\"\n"
|
||||
result += " },\n"
|
||||
result += appconstants.JSONCloseBrace
|
||||
result += " \"devDependencies\": {\n"
|
||||
result += " \"jest\": \"^29.0.0\",\n"
|
||||
result += " \"webpack\": \"^5.0.0\"\n"
|
||||
@@ -356,12 +350,12 @@ var PackageJSONContent = func() string {
|
||||
func NewFixtureManager() *FixtureManager {
|
||||
_, currentFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
panic("failed to get current file path")
|
||||
panic(appconstants.ErrFailedToGetCurrentFilePath)
|
||||
}
|
||||
|
||||
// Get the project root (go up from testutil/fixtures.go to project root)
|
||||
projectRoot := filepath.Dir(filepath.Dir(currentFile))
|
||||
basePath := filepath.Join(projectRoot, "testdata", "yaml-fixtures")
|
||||
basePath := filepath.Join(projectRoot, appconstants.DirTestdata, appconstants.DirYAMLFixtures)
|
||||
|
||||
return &FixtureManager{
|
||||
basePath: basePath,
|
||||
@@ -449,8 +443,10 @@ func (fm *FixtureManager) LoadActionFixture(name string) (*ActionFixture, error)
|
||||
// LoadConfigFixture loads a configuration fixture.
|
||||
func (fm *FixtureManager) LoadConfigFixture(name string) (*ConfigFixture, error) {
|
||||
configPath := filepath.Join(fm.basePath, "configs", name)
|
||||
if !strings.HasSuffix(configPath, YmlExtension) && !strings.HasSuffix(configPath, YamlExtension) {
|
||||
configPath += YmlExtension
|
||||
hasYMLExt := strings.HasSuffix(configPath, appconstants.ActionFileExtYML)
|
||||
hasYAMLExt := strings.HasSuffix(configPath, appconstants.ActionFileExtYAML)
|
||||
if !hasYMLExt && !hasYAMLExt {
|
||||
configPath += appconstants.ActionFileExtYML
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(configPath) // #nosec G304 -- test fixture path from project structure
|
||||
@@ -537,8 +533,10 @@ func (fm *FixtureManager) resolveFixturePath(name string) string {
|
||||
|
||||
// ensureYamlExtension adds YAML extension if not present.
|
||||
func (fm *FixtureManager) ensureYamlExtension(path string) string {
|
||||
if !strings.HasSuffix(path, YmlExtension) && !strings.HasSuffix(path, YamlExtension) {
|
||||
path += YmlExtension
|
||||
hasYMLExt := strings.HasSuffix(path, appconstants.ActionFileExtYML)
|
||||
hasYAMLExt := strings.HasSuffix(path, appconstants.ActionFileExtYAML)
|
||||
if !hasYMLExt && !hasYAMLExt {
|
||||
path += appconstants.ActionFileExtYML
|
||||
}
|
||||
|
||||
return path
|
||||
@@ -626,7 +624,7 @@ func (fm *FixtureManager) determineActionTypeByContent(content string) ActionTyp
|
||||
// determineConfigType determines the type of configuration fixture.
|
||||
func (fm *FixtureManager) determineConfigType(name string) string {
|
||||
if strings.Contains(name, "global") {
|
||||
return "global"
|
||||
return appconstants.ScopeGlobal
|
||||
}
|
||||
if strings.Contains(name, "repo") {
|
||||
return "repo-specific"
|
||||
@@ -730,7 +728,9 @@ func (fm *FixtureManager) scenarioMatchesTags(scenario *TestScenario, tags []str
|
||||
// createDefaultScenarios creates a default scenarios file.
|
||||
func (fm *FixtureManager) createDefaultScenarios(scenarioFile string) error {
|
||||
// Ensure the directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(scenarioFile), 0750); err != nil { // #nosec G301 -- test directory permissions
|
||||
scenarioDir := filepath.Dir(scenarioFile)
|
||||
// #nosec G301 -- test directory permissions
|
||||
if err := os.MkdirAll(scenarioDir, appconstants.FilePermDir); err != nil {
|
||||
return fmt.Errorf("failed to create scenarios directory: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,23 +61,9 @@ func TestMustReadFixture_Panic(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing file panics", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("expected panic but got none")
|
||||
} else {
|
||||
errStr, ok := r.(string)
|
||||
if !ok {
|
||||
t.Errorf("expected panic to contain string message, got: %T", r)
|
||||
|
||||
return
|
||||
}
|
||||
if !strings.Contains(errStr, "failed to read fixture") {
|
||||
t.Errorf("expected panic message about fixture reading, got: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mustReadFixture("nonexistent-file.yml")
|
||||
ExpectPanic(t, func() {
|
||||
mustReadFixture("nonexistent-file.yml")
|
||||
}, "failed to read fixture")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-github/v74/github"
|
||||
)
|
||||
|
||||
// File constants.
|
||||
const (
|
||||
readmeFilename = "README.md"
|
||||
"github.com/ivuorinen/gh-action-readme/appconstants"
|
||||
)
|
||||
|
||||
// TestExecutor is a function type for executing specific types of tests.
|
||||
@@ -355,7 +352,7 @@ func executeTest(t *testing.T, testCase TestCase, ctx *TestContext) *TestResult
|
||||
}
|
||||
|
||||
// Create temporary action file
|
||||
actionPath := filepath.Join(ctx.TempDir, "action.yml")
|
||||
actionPath := filepath.Join(ctx.TempDir, appconstants.ActionFileNameYML)
|
||||
WriteTestFile(t, actionPath, fixture.Content)
|
||||
}
|
||||
|
||||
@@ -571,23 +568,23 @@ func DetectGeneratedFiles(outputDir string, outputFormat string) []string {
|
||||
if !entry.IsDir() {
|
||||
name := entry.Name()
|
||||
// Skip the action.yml we created for testing
|
||||
if name == "action.yml" {
|
||||
if name == appconstants.ActionFileNameYML {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this file matches the expected output format
|
||||
isGenerated := false
|
||||
switch outputFormat {
|
||||
case "md":
|
||||
isGenerated = name == readmeFilename
|
||||
case "html":
|
||||
case appconstants.OutputFormatMarkdown:
|
||||
isGenerated = name == appconstants.ReadmeMarkdown
|
||||
case appconstants.OutputFormatHTML:
|
||||
isGenerated = strings.HasSuffix(name, ".html")
|
||||
case "json":
|
||||
isGenerated = name == "action-docs.json"
|
||||
case "asciidoc":
|
||||
isGenerated = name == "README.adoc"
|
||||
case appconstants.OutputFormatJSON:
|
||||
isGenerated = name == appconstants.ActionDocsJSON
|
||||
case appconstants.OutputFormatASCIIDoc:
|
||||
isGenerated = name == appconstants.ReadmeASCIIDoc
|
||||
default:
|
||||
isGenerated = name == readmeFilename
|
||||
isGenerated = name == appconstants.ReadmeMarkdown
|
||||
}
|
||||
|
||||
if isGenerated {
|
||||
@@ -603,7 +600,7 @@ func DetectGeneratedFiles(outputDir string, outputFormat string) []string {
|
||||
func DefaultTestConfig() *TestConfig {
|
||||
return &TestConfig{
|
||||
Theme: "default",
|
||||
OutputFormat: "md",
|
||||
OutputFormat: appconstants.OutputFormatMarkdown,
|
||||
OutputDir: ".",
|
||||
Verbose: false,
|
||||
Quiet: false,
|
||||
@@ -652,7 +649,7 @@ func CreateTemporaryAction(t *testing.T, fixture string) string {
|
||||
// Load the fixture
|
||||
actionFixture, err := LoadActionFixture(fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load action fixture %s: %v", fixture, err)
|
||||
t.Fatalf(appconstants.ErrFailedToLoadActionFixture, fixture, err)
|
||||
}
|
||||
|
||||
// Create temporary directory
|
||||
@@ -660,7 +657,7 @@ func CreateTemporaryAction(t *testing.T, fixture string) string {
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
// Write action file
|
||||
actionPath := filepath.Join(tempDir, "action.yml")
|
||||
actionPath := filepath.Join(tempDir, appconstants.ActionFileNameYML)
|
||||
WriteTestFile(t, actionPath, actionFixture.Content)
|
||||
|
||||
return actionPath
|
||||
@@ -673,7 +670,7 @@ func CreateTemporaryActionDir(t *testing.T, fixture string) string {
|
||||
// Load the fixture
|
||||
actionFixture, err := LoadActionFixture(fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load action fixture %s: %v", fixture, err)
|
||||
t.Fatalf(appconstants.ErrFailedToLoadActionFixture, fixture, err)
|
||||
}
|
||||
|
||||
// Create temporary directory
|
||||
@@ -681,7 +678,7 @@ func CreateTemporaryActionDir(t *testing.T, fixture string) string {
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
// Write action file
|
||||
actionPath := filepath.Join(tempDir, "action.yml")
|
||||
actionPath := filepath.Join(tempDir, appconstants.ActionFileNameYML)
|
||||
WriteTestFile(t, actionPath, actionFixture.Content)
|
||||
|
||||
return tempDir
|
||||
@@ -841,7 +838,12 @@ func TestAllThemes(t *testing.T, testFunc func(*testing.T, string)) {
|
||||
func TestAllFormats(t *testing.T, testFunc func(*testing.T, string)) {
|
||||
t.Helper()
|
||||
|
||||
formats := []string{"md", "html", "json", "asciidoc"}
|
||||
formats := []string{
|
||||
appconstants.OutputFormatMarkdown,
|
||||
appconstants.OutputFormatHTML,
|
||||
appconstants.OutputFormatJSON,
|
||||
appconstants.OutputFormatASCIIDoc,
|
||||
}
|
||||
|
||||
for _, format := range formats {
|
||||
format := format // capture loop variable
|
||||
@@ -888,8 +890,7 @@ func CreateGitHubMockSuite(scenarios []string) *MockSuite {
|
||||
func AssertFixtureValid(t *testing.T, fixtureName string) {
|
||||
t.Helper()
|
||||
|
||||
fixture, err := LoadActionFixture(fixtureName)
|
||||
AssertNoError(t, err)
|
||||
fixture := MustLoadActionFixture(t, fixtureName)
|
||||
|
||||
if !fixture.IsValid {
|
||||
t.Errorf("fixture %s should be valid but failed validation", fixtureName)
|
||||
@@ -974,18 +975,18 @@ func CreateActionTestCases() []ActionTestCase {
|
||||
// getExpectedFilename returns the expected filename for a given output format.
|
||||
func getExpectedFilename(outputFormat string) string {
|
||||
switch outputFormat {
|
||||
case "md":
|
||||
return "README.md"
|
||||
case "html":
|
||||
case appconstants.OutputFormatMarkdown:
|
||||
return appconstants.ReadmeMarkdown
|
||||
case appconstants.OutputFormatHTML:
|
||||
// HTML files have variable names based on action name, so we'll use a pattern
|
||||
// The DetectGeneratedFiles function will find any .html file
|
||||
return "*.html"
|
||||
case "json":
|
||||
return "action-docs.json"
|
||||
case "asciidoc":
|
||||
return "README.adoc"
|
||||
case appconstants.OutputFormatJSON:
|
||||
return appconstants.ActionDocsJSON
|
||||
case appconstants.OutputFormatASCIIDoc:
|
||||
return appconstants.ReadmeASCIIDoc
|
||||
default:
|
||||
return "README.md"
|
||||
return appconstants.ReadmeMarkdown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,7 +994,12 @@ func getExpectedFilename(outputFormat string) string {
|
||||
func CreateGeneratorTestCases() []GeneratorTestCase {
|
||||
validFixtures := GetValidFixtures()
|
||||
themes := []string{"default", "github", "minimal", "professional"}
|
||||
formats := []string{"md", "html", "json", "asciidoc"}
|
||||
formats := []string{
|
||||
appconstants.OutputFormatMarkdown,
|
||||
appconstants.OutputFormatHTML,
|
||||
appconstants.OutputFormatJSON,
|
||||
appconstants.OutputFormatASCIIDoc,
|
||||
}
|
||||
|
||||
cases := make([]GeneratorTestCase, 0)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v74/github"
|
||||
|
||||
"github.com/ivuorinen/gh-action-readme/appconstants"
|
||||
)
|
||||
|
||||
// MockHTTPClient is a mock HTTP client for testing.
|
||||
@@ -91,20 +93,155 @@ func TempDir(t *testing.T) (string, func()) {
|
||||
}
|
||||
}
|
||||
|
||||
// CleanupCache provides a standard cache cleanup helper for deferred cleanup.
|
||||
// It returns a function that closes the cache and fails the test on errors.
|
||||
func CleanupCache(tb testing.TB, cache interface{ Close() error }) func() {
|
||||
tb.Helper()
|
||||
|
||||
return func() {
|
||||
tb.Helper()
|
||||
if err := cache.Close(); err != nil {
|
||||
tb.Fatalf("failed to close cache: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExpectPanic asserts that the provided function panics with a message containing the expected substring.
|
||||
// This helper reduces panic recovery test boilerplate from 12-15 lines to 3-4 lines.
|
||||
func ExpectPanic(t *testing.T, fn func(), expectedSubstring string) {
|
||||
t.Helper()
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("expected panic but got none")
|
||||
} else {
|
||||
var errStr string
|
||||
switch v := r.(type) {
|
||||
case string:
|
||||
errStr = v
|
||||
case error:
|
||||
errStr = v.Error()
|
||||
default:
|
||||
errStr = fmt.Sprintf("%v", v)
|
||||
}
|
||||
if !strings.Contains(errStr, expectedSubstring) {
|
||||
t.Errorf("expected panic message containing %q, got: %v", expectedSubstring, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
|
||||
// MustLoadActionFixture loads an action fixture and fails the test on error.
|
||||
// This helper consolidates the load + assertion pattern.
|
||||
func MustLoadActionFixture(t *testing.T, path string) *ActionFixture {
|
||||
t.Helper()
|
||||
fixture, err := LoadActionFixture(path)
|
||||
AssertNoError(t, err)
|
||||
|
||||
return fixture
|
||||
}
|
||||
|
||||
// LoadAndWriteFixture loads an action fixture and writes it to the specified path.
|
||||
// This helper reduces the common 3-line pattern to a single line.
|
||||
func LoadAndWriteFixture(t *testing.T, fixturePath, targetPath string) {
|
||||
t.Helper()
|
||||
fixture := MustLoadActionFixture(t, fixturePath)
|
||||
WriteTestFile(t, targetPath, fixture.Content)
|
||||
}
|
||||
|
||||
// WriteTestFile writes a test file to the given path.
|
||||
func WriteTestFile(t *testing.T, path, content string) {
|
||||
t.Helper()
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0750); err != nil { // #nosec G301 -- test directory permissions
|
||||
// #nosec G301 -- test directory permissions
|
||||
if err := os.MkdirAll(dir, appconstants.FilePermDir); err != nil {
|
||||
t.Fatalf("failed to create dir %s: %v", dir, err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, []byte(content), 0600); err != nil { // #nosec G306 -- test file permissions
|
||||
// #nosec G306 -- test file permissions
|
||||
if err := os.WriteFile(path, []byte(content), appconstants.FilePermDefault); err != nil {
|
||||
t.Fatalf("failed to write test file %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteActionFixture writes an action fixture to a standard action.yml file.
|
||||
func WriteActionFixture(t *testing.T, dir, fixturePath string) string {
|
||||
t.Helper()
|
||||
actionPath := filepath.Join(dir, appconstants.TestPathActionYML)
|
||||
fixture := MustLoadActionFixture(t, fixturePath)
|
||||
WriteTestFile(t, actionPath, fixture.Content)
|
||||
|
||||
return actionPath
|
||||
}
|
||||
|
||||
// WriteActionFixtureAs writes an action fixture with a custom filename.
|
||||
func WriteActionFixtureAs(t *testing.T, dir, filename, fixturePath string) string {
|
||||
t.Helper()
|
||||
actionPath := filepath.Join(dir, filename)
|
||||
fixture := MustLoadActionFixture(t, fixturePath)
|
||||
WriteTestFile(t, actionPath, fixture.Content)
|
||||
|
||||
return actionPath
|
||||
}
|
||||
|
||||
// CreateConfigDir creates a standard .config/gh-action-readme directory.
|
||||
func CreateConfigDir(t *testing.T, baseDir string) string {
|
||||
t.Helper()
|
||||
configDir := filepath.Join(baseDir, appconstants.TestDirConfigGhActionReadme)
|
||||
// #nosec G301 -- test directory permissions
|
||||
if err := os.MkdirAll(configDir, appconstants.FilePermDir); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
return configDir
|
||||
}
|
||||
|
||||
// WriteConfigFile writes a config file to the standard location.
|
||||
func WriteConfigFile(t *testing.T, baseDir, content string) string {
|
||||
t.Helper()
|
||||
configDir := CreateConfigDir(t, baseDir)
|
||||
configPath := filepath.Join(configDir, appconstants.ConfigFileNameFull)
|
||||
WriteTestFile(t, configPath, content)
|
||||
|
||||
return configPath
|
||||
}
|
||||
|
||||
// CreateActionSubdir creates a subdirectory and writes an action fixture to it.
|
||||
func CreateActionSubdir(t *testing.T, baseDir, subdirName, fixturePath string) string {
|
||||
t.Helper()
|
||||
subDir := filepath.Join(baseDir, subdirName)
|
||||
// #nosec G301 -- test directory permissions
|
||||
if err := os.MkdirAll(subDir, appconstants.FilePermDir); err != nil {
|
||||
t.Fatalf("failed to create subdir: %v", err)
|
||||
}
|
||||
|
||||
return WriteActionFixture(t, subDir, fixturePath)
|
||||
}
|
||||
|
||||
// AssertFileExists fails if the file does not exist.
|
||||
func AssertFileExists(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
t.Fatalf("expected file to exist: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertFileNotExists fails if the file exists.
|
||||
func AssertFileNotExists(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
// File exists
|
||||
t.Fatalf("expected file not to exist: %s", path)
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
// Error occurred but it's not a "does not exist" error
|
||||
t.Fatalf("error checking file existence: %v", err)
|
||||
}
|
||||
// err != nil && os.IsNotExist(err) - this is the success case
|
||||
}
|
||||
|
||||
// MockColoredOutput captures output for testing.
|
||||
type MockColoredOutput struct {
|
||||
Messages []string
|
||||
@@ -192,14 +329,14 @@ func CreateTestAction(name, description string, inputs map[string]string) string
|
||||
inputsYAML.WriteString(fmt.Sprintf(" %s:\n description: %s\n required: true\n", key, desc))
|
||||
}
|
||||
|
||||
result := fmt.Sprintf("name: %s\n", name)
|
||||
result += fmt.Sprintf("description: %s\n", description)
|
||||
result := fmt.Sprintf(appconstants.YAMLFieldName, name)
|
||||
result += fmt.Sprintf(appconstants.YAMLFieldDescription, description)
|
||||
result += "inputs:\n"
|
||||
result += inputsYAML.String()
|
||||
result += "outputs:\n"
|
||||
result += " result:\n"
|
||||
result += " description: 'The result'\n"
|
||||
result += "runs:\n"
|
||||
result += appconstants.YAMLFieldRuns
|
||||
result += " using: 'node20'\n"
|
||||
result += " main: 'index.js'\n"
|
||||
result += "branding:\n"
|
||||
@@ -220,16 +357,17 @@ func SetupTestTemplates(t *testing.T, dir string) {
|
||||
// Create directories
|
||||
for _, theme := range []string{"github", "gitlab", "minimal", "professional"} {
|
||||
themeDir := filepath.Join(themesDir, theme)
|
||||
if err := os.MkdirAll(themeDir, 0750); err != nil { // #nosec G301 -- test directory permissions
|
||||
// #nosec G301 -- test directory permissions
|
||||
if err := os.MkdirAll(themeDir, appconstants.FilePermDir); err != nil {
|
||||
t.Fatalf("failed to create theme dir %s: %v", themeDir, err)
|
||||
}
|
||||
// Write theme template
|
||||
templatePath := filepath.Join(themeDir, "readme.tmpl")
|
||||
templatePath := filepath.Join(themeDir, appconstants.TemplateReadme)
|
||||
WriteTestFile(t, templatePath, SimpleTemplate)
|
||||
}
|
||||
|
||||
// Create default template
|
||||
defaultTemplatePath := filepath.Join(templatesDir, "readme.tmpl")
|
||||
defaultTemplatePath := filepath.Join(templatesDir, appconstants.TemplateReadme)
|
||||
WriteTestFile(t, defaultTemplatePath, SimpleTemplate)
|
||||
}
|
||||
|
||||
@@ -240,9 +378,9 @@ func CreateCompositeAction(name, description string, steps []string) string {
|
||||
stepsYAML.WriteString(fmt.Sprintf(" - name: Step %d\n uses: %s\n", i+1, step))
|
||||
}
|
||||
|
||||
result := fmt.Sprintf("name: %s\n", name)
|
||||
result += fmt.Sprintf("description: %s\n", description)
|
||||
result += "runs:\n"
|
||||
result := fmt.Sprintf(appconstants.YAMLFieldName, name)
|
||||
result += fmt.Sprintf(appconstants.YAMLFieldDescription, description)
|
||||
result += appconstants.YAMLFieldRuns
|
||||
result += " using: 'composite'\n"
|
||||
result += " steps:\n"
|
||||
result += stepsYAML.String()
|
||||
@@ -373,6 +511,27 @@ func AssertEqual(t *testing.T, expected, actual any) {
|
||||
}
|
||||
}
|
||||
|
||||
// AssertSliceContainsAll fails if any of expectedSubstrings is not found in any item of the slice.
|
||||
// This is useful for checking that suggestions or messages contain expected content.
|
||||
func AssertSliceContainsAll(t *testing.T, slice []string, expectedSubstrings []string) {
|
||||
t.Helper()
|
||||
|
||||
if len(slice) == 0 {
|
||||
t.Fatal("slice is empty")
|
||||
}
|
||||
|
||||
allItems := strings.Join(slice, " ")
|
||||
for _, expected := range expectedSubstrings {
|
||||
if !strings.Contains(allItems, expected) {
|
||||
t.Errorf(
|
||||
"expected to find %q in slice, got:\n%s",
|
||||
expected,
|
||||
strings.Join(slice, "\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewStringReader creates an io.ReadCloser from a string.
|
||||
func NewStringReader(s string) io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(s))
|
||||
@@ -392,8 +551,8 @@ func GetGitHubTokenHierarchyTests() []GitHubTokenTestCase {
|
||||
Name: "GH_README_GITHUB_TOKEN has highest priority",
|
||||
SetupFunc: func(t *testing.T) func() {
|
||||
t.Helper()
|
||||
cleanup1 := SetEnv(t, "GH_README_GITHUB_TOKEN", "priority-token")
|
||||
cleanup2 := SetEnv(t, "GITHUB_TOKEN", "fallback-token")
|
||||
cleanup1 := SetEnv(t, appconstants.EnvGitHubToken, "priority-token")
|
||||
cleanup2 := SetEnv(t, appconstants.EnvGitHubTokenStandard, appconstants.TokenFallback)
|
||||
|
||||
return func() {
|
||||
cleanup1()
|
||||
@@ -406,19 +565,19 @@ func GetGitHubTokenHierarchyTests() []GitHubTokenTestCase {
|
||||
Name: "GITHUB_TOKEN as fallback",
|
||||
SetupFunc: func(t *testing.T) func() {
|
||||
t.Helper()
|
||||
_ = os.Unsetenv("GH_README_GITHUB_TOKEN")
|
||||
cleanup := SetEnv(t, "GITHUB_TOKEN", "fallback-token")
|
||||
_ = os.Unsetenv(appconstants.EnvGitHubToken)
|
||||
cleanup := SetEnv(t, appconstants.EnvGitHubTokenStandard, appconstants.TokenFallback)
|
||||
|
||||
return cleanup
|
||||
},
|
||||
ExpectedToken: "fallback-token",
|
||||
ExpectedToken: appconstants.TokenFallback,
|
||||
},
|
||||
{
|
||||
Name: "no environment variables",
|
||||
SetupFunc: func(t *testing.T) func() {
|
||||
t.Helper()
|
||||
_ = os.Unsetenv("GH_README_GITHUB_TOKEN")
|
||||
_ = os.Unsetenv("GITHUB_TOKEN")
|
||||
_ = os.Unsetenv(appconstants.EnvGitHubToken)
|
||||
_ = os.Unsetenv(appconstants.EnvGitHubTokenStandard)
|
||||
|
||||
return func() {}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user