Files
gh-action-readme/testutil/test_suites.go
Ismo Vuorinen 3fbb608f9f feat: update go version, renovate config, tooling, fixes (#28)
* feat(deps): update go version, renovate config, tooling

* chore(deps): update google/go-github to v74

* feat(deps): migrate from yaml.v3 to goccy/go-yaml

* chore(deps): update goccy/go-yaml to v1.18.0 and address security concerns

* feat: improve issue templates and project configuration

- Update GitHub issue templates with CLI-specific fields for better bug reports
- Add specialized templates for documentation, theme, and performance issues
- Update pre-commit config to include comprehensive documentation linting
- Remove outdated Snyk configuration and security references
- Update Go version from 1.23+ to 1.24+ across project
- Streamline README.md organization and improve clarity
- Update CHANGELOG.md and CLAUDE.md formatting
- Create comprehensive CONTRIBUTING.md with development guidelines
- Remove TODO.md (replaced by docs/roadmap.md)
- Move SECURITY.md to docs/security.md

* docs: fix markdown linting violations across documentation

* fix: resolve template placeholder issues and improve uses statement generation

* fix: remove trailing whitespace from GitHub issue template
2025-08-07 05:22:44 +03:00

1065 lines
26 KiB
Go

// Package testutil provides table-driven testing infrastructure and test helpers for gh-action-readme.
package testutil
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/go-github/v74/github"
)
// File constants.
const (
readmeFilename = "README.md"
)
// TestExecutor is a function type for executing specific types of tests.
type TestExecutor func(t *testing.T, testCase TestCase, ctx *TestContext) *TestResult
// TestCase represents a parameterized test case.
type TestCase struct {
Name string
Description string
Fixture string
Config *TestConfig
Mocks *MockConfig
Expected *ExpectedResult
SetupFunc func(*testing.T, *TestContext) error
CleanupFunc func(*testing.T, *TestContext) error
SkipReason string
Executor TestExecutor // Custom test execution logic
}
// TestSuite represents a collection of related test cases.
type TestSuite struct {
Name string
Description string
Cases []TestCase
GlobalSetup func(*testing.T) (*TestContext, error)
GlobalCleanup func(*testing.T, *TestContext) error
Parallel bool
}
// TestConfig holds configuration options for a test case.
type TestConfig struct {
Theme string
OutputFormat string
OutputDir string
Verbose bool
Quiet bool
Recursive bool
Validate bool
ExtraFlags map[string]string
}
// MockConfig specifies which mocks to set up for a test case.
type MockConfig struct {
GitHubClient bool
GitHubResponses map[string]string
ColoredOutput bool
FileSystem bool
Environment map[string]string
TempDir bool
}
// ExpectedResult defines what a test case should produce.
type ExpectedResult struct {
ShouldSucceed bool
ShouldFail bool
ExpectedError string
ExpectedOutput []string
ExpectedFiles []string
ExpectedExitCode int
CustomValidation func(*testing.T, *TestResult) error
}
// TestContext holds the context for a running test.
type TestContext struct {
TempDir string
FixtureManager *FixtureManager
Mocks *MockSuite
Config *TestConfig
Cleanup []func() error
}
// TestResult holds the results of a test execution.
type TestResult struct {
Success bool
Error error
Output string
Files []string
ExitCode int
Duration int64
Context *TestContext
}
// MockSuite holds all configured mocks for a test.
type MockSuite struct {
GitHubClient *github.Client
ColoredOutput *MockColoredOutput
HTTPClient *MockHTTPClient
Environment map[string]string
TempDirs []string
}
// ActionTestCase represents a test case specifically for action-related testing.
type ActionTestCase struct {
TestCase
ActionType ActionType
ExpectValid bool
ExpectAnalysis bool
ExpectDeps int
}
// GeneratorTestCase represents a test case for generator testing.
type GeneratorTestCase struct {
TestCase
Theme string
OutputFormat string
ExpectFiles []string
ExpectContent map[string][]string
}
// ValidationTestCase represents a test case for validation testing.
type ValidationTestCase struct {
TestCase
ExpectValid bool
ExpectedErrors []string
ValidationLevel string
}
// RunTestSuite executes a complete test suite with common setup/teardown.
func RunTestSuite(t *testing.T, suite TestSuite) {
t.Helper()
if suite.Name == "" {
t.Fatal("test suite must have a name")
}
t.Run(suite.Name, func(t *testing.T) {
if suite.Parallel {
t.Parallel()
}
globalContext := setupGlobalContext(t, suite)
runAllTestCases(t, suite, globalContext)
})
}
// setupGlobalContext handles global setup and cleanup for test suite.
func setupGlobalContext(t *testing.T, suite TestSuite) *TestContext {
t.Helper()
var globalContext *TestContext
if suite.GlobalSetup != nil {
var err error
globalContext, err = suite.GlobalSetup(t)
if err != nil {
t.Fatalf("global setup failed: %v", err)
}
}
// Set up global cleanup
if suite.GlobalCleanup != nil && globalContext != nil {
t.Cleanup(func() {
if err := suite.GlobalCleanup(t, globalContext); err != nil {
t.Errorf("global cleanup failed: %v", err)
}
})
}
return globalContext
}
// runAllTestCases executes all test cases in the suite.
func runAllTestCases(t *testing.T, suite TestSuite, globalContext *TestContext) {
t.Helper()
for _, testCase := range suite.Cases {
testCase := testCase // capture loop variable
if testCase.SkipReason != "" {
runSkippedTest(t, testCase)
continue
}
runIndividualTest(t, suite, testCase, globalContext)
}
}
// runSkippedTest handles skipped test cases.
func runSkippedTest(t *testing.T, testCase TestCase) {
t.Helper()
t.Run(testCase.Name, func(t *testing.T) {
t.Skip(testCase.SkipReason)
})
}
// runIndividualTest executes a single test case.
func runIndividualTest(t *testing.T, suite TestSuite, testCase TestCase, globalContext *TestContext) {
t.Helper()
t.Run(testCase.Name, func(t *testing.T) {
if suite.Parallel {
t.Parallel()
}
runTestCase(t, testCase, globalContext)
})
}
// runTestCase executes a single test case.
func runTestCase(t *testing.T, testCase TestCase, globalContext *TestContext) {
t.Helper()
// Create test context
ctx := createTestContext(t, testCase, globalContext)
// Setup test-specific cleanup
defer func() {
for i := len(ctx.Cleanup) - 1; i >= 0; i-- {
if err := ctx.Cleanup[i](); err != nil {
t.Errorf("cleanup failed: %v", err)
}
}
}()
// Run test case setup
if testCase.SetupFunc != nil {
if err := testCase.SetupFunc(t, ctx); err != nil {
t.Fatalf("test setup failed: %v", err)
}
}
// Execute the test
result := executeTest(t, testCase, ctx)
// Validate results
validateTestResult(t, testCase, result)
// Run test case cleanup
if testCase.CleanupFunc != nil {
if err := testCase.CleanupFunc(t, ctx); err != nil {
t.Errorf("test cleanup failed: %v", err)
}
}
}
// createTestContext creates a test context for a test case.
func createTestContext(t *testing.T, testCase TestCase, globalContext *TestContext) *TestContext {
t.Helper()
ctx := &TestContext{
FixtureManager: GetFixtureManager(),
Config: testCase.Config,
Cleanup: make([]func() error, 0),
}
// Inherit from global context if available
if globalContext != nil {
if ctx.Config == nil {
ctx.Config = globalContext.Config
}
if globalContext.TempDir != "" {
ctx.TempDir = globalContext.TempDir
}
}
// Set up temporary directory if needed
if testCase.Mocks != nil && testCase.Mocks.TempDir {
tempDir, cleanup := TempDir(t)
ctx.TempDir = tempDir
ctx.Cleanup = append(ctx.Cleanup, func() error {
cleanup()
return nil
})
}
// Set up mocks
if testCase.Mocks != nil {
ctx.Mocks = createMockSuite(t, testCase.Mocks)
}
return ctx
}
// createMockSuite creates a mock suite based on configuration.
func createMockSuite(t *testing.T, config *MockConfig) *MockSuite {
t.Helper()
suite := &MockSuite{
Environment: make(map[string]string),
TempDirs: make([]string, 0),
}
// Set up GitHub client mock
if config.GitHubClient {
responses := config.GitHubResponses
if responses == nil {
responses = MockGitHubResponses()
}
suite.GitHubClient = MockGitHubClient(responses)
}
// Set up colored output mock
if config.ColoredOutput {
suite.ColoredOutput = &MockColoredOutput{
Messages: make([]string, 0),
}
}
// Set up HTTP client mock
if config.GitHubClient {
suite.HTTPClient = &MockHTTPClient{
Responses: make(map[string]*http.Response),
Requests: make([]*http.Request, 0),
}
}
// Set up environment variables
for key, value := range config.Environment {
suite.Environment[key] = value
}
return suite
}
// executeTest runs the actual test logic.
func executeTest(t *testing.T, testCase TestCase, ctx *TestContext) *TestResult {
t.Helper()
// Use custom executor if provided
if testCase.Executor != nil {
return testCase.Executor(t, testCase, ctx)
}
// Default execution: just create fixture and return success
result := &TestResult{
Context: ctx,
}
// If we have a fixture, load it and create action file
if testCase.Fixture != "" {
fixture, err := ctx.FixtureManager.LoadActionFixture(testCase.Fixture)
if err != nil {
result.Error = fmt.Errorf("failed to load fixture %s: %w", testCase.Fixture, err)
return result
}
// Create temporary action file
actionPath := filepath.Join(ctx.TempDir, "action.yml")
WriteTestFile(t, actionPath, fixture.Content)
}
// Default success for non-generator tests
result.Success = true
return result
}
// validateTestResult validates the test results against expectations.
func validateTestResult(t *testing.T, testCase TestCase, result *TestResult) {
t.Helper()
if testCase.Expected == nil {
return
}
expected := testCase.Expected
validateSuccessFailure(t, expected, result)
validateError(t, expected, result)
validateOutput(t, expected, result)
validateFiles(t, expected, result)
validateExitCode(t, expected, result)
validateCustom(t, expected, result)
}
// validateSuccessFailure checks success/failure expectations.
func validateSuccessFailure(t *testing.T, expected *ExpectedResult, result *TestResult) {
t.Helper()
if expected.ShouldSucceed && !result.Success {
t.Errorf("expected test to succeed, but it failed: %v", result.Error)
}
if expected.ShouldFail && result.Success {
t.Error("expected test to fail, but it succeeded")
}
}
// validateError checks expected error conditions.
func validateError(t *testing.T, expected *ExpectedResult, result *TestResult) {
t.Helper()
if expected.ExpectedError == "" {
return
}
if result.Error == nil {
t.Errorf("expected error %q, but got no error", expected.ExpectedError)
return
}
if result.Error.Error() != expected.ExpectedError {
t.Errorf("expected error %q, but got %q", expected.ExpectedError, result.Error.Error())
}
}
// validateOutput checks expected output conditions.
func validateOutput(t *testing.T, expected *ExpectedResult, result *TestResult) {
t.Helper()
for _, expectedOutput := range expected.ExpectedOutput {
if !containsString(result.Output, expectedOutput) {
t.Errorf("expected output to contain %q, but it didn't. Output: %s", expectedOutput, result.Output)
}
}
}
// validateFiles checks expected file conditions.
func validateFiles(t *testing.T, expected *ExpectedResult, result *TestResult) {
t.Helper()
for _, expectedFile := range expected.ExpectedFiles {
if strings.Contains(expectedFile, "*") {
// Handle wildcard patterns like "*.html"
found := false
pattern := strings.TrimPrefix(expectedFile, "*")
for _, actualFile := range result.Files {
if strings.HasSuffix(actualFile, pattern) {
found = true
break
}
}
if !found {
t.Errorf(
"expected file matching pattern %q to be created, but none found. Files: %v",
expectedFile,
result.Files,
)
}
} else if !containsString(result.Files, expectedFile) {
// Handle exact filename matches
t.Errorf("expected file %q to be created, but it wasn't. Files: %v", expectedFile, result.Files)
}
}
}
// validateExitCode checks expected exit code.
func validateExitCode(t *testing.T, expected *ExpectedResult, result *TestResult) {
t.Helper()
if expected.ExpectedExitCode != 0 && result.ExitCode != expected.ExpectedExitCode {
t.Errorf("expected exit code %d, but got %d", expected.ExpectedExitCode, result.ExitCode)
}
}
// validateCustom runs custom validation if provided.
func validateCustom(t *testing.T, expected *ExpectedResult, result *TestResult) {
t.Helper()
if expected.CustomValidation == nil {
return
}
if err := expected.CustomValidation(t, result); err != nil {
t.Errorf("custom validation failed: %v", err)
}
}
// Helper functions for specific test types
// RunActionTests executes action-related test cases.
func RunActionTests(t *testing.T, cases []ActionTestCase) {
t.Helper()
testCases := make([]TestCase, len(cases))
for i, actionCase := range cases {
testCases[i] = actionCase.TestCase
}
suite := TestSuite{
Name: "Action Tests",
Cases: testCases,
Parallel: true,
}
RunTestSuite(t, suite)
}
// RunGeneratorTests executes generator test cases.
func RunGeneratorTests(t *testing.T, cases []GeneratorTestCase) {
t.Helper()
testCases := make([]TestCase, len(cases))
for i, genCase := range cases {
testCases[i] = genCase.TestCase
}
suite := TestSuite{
Name: "Generator Tests",
Cases: testCases,
Parallel: true,
}
RunTestSuite(t, suite)
}
// RunValidationTests executes validation test cases.
func RunValidationTests(t *testing.T, cases []ValidationTestCase) {
t.Helper()
testCases := make([]TestCase, len(cases))
for i, valCase := range cases {
testCases[i] = valCase.TestCase
}
suite := TestSuite{
Name: "Validation Tests",
Cases: testCases,
Parallel: true,
}
RunTestSuite(t, suite)
}
// Utility functions
// containsString checks if a slice contains a string.
func containsString(slice any, item string) bool {
switch s := slice.(type) {
case []string:
for _, v := range s {
if v == item {
return true
}
}
case string:
return len(s) > 0 && s == item
}
return false
}
// DetectGeneratedFiles finds files that were generated in the output directory.
// This is exported so tests in other packages can use it.
func DetectGeneratedFiles(outputDir string, outputFormat string) []string {
var files []string
// Different output formats create different files:
// - md: README.md
// - html: <action-name>.html (name varies)
// - json: action-docs.json
// - asciidoc: README.adoc
entries, err := os.ReadDir(outputDir)
if err != nil {
return files
}
for _, entry := range entries {
if !entry.IsDir() {
name := entry.Name()
// Skip the action.yml we created for testing
if name == "action.yml" {
continue
}
// Check if this file matches the expected output format
isGenerated := false
switch outputFormat {
case "md":
isGenerated = name == readmeFilename
case "html":
isGenerated = strings.HasSuffix(name, ".html")
case "json":
isGenerated = name == "action-docs.json"
case "asciidoc":
isGenerated = name == "README.adoc"
default:
isGenerated = name == readmeFilename
}
if isGenerated {
files = append(files, name)
}
}
}
return files
}
// DefaultTestConfig returns a default test configuration.
func DefaultTestConfig() *TestConfig {
return &TestConfig{
Theme: "default",
OutputFormat: "md",
OutputDir: ".",
Verbose: false,
Quiet: false,
Recursive: false,
Validate: true,
ExtraFlags: make(map[string]string),
}
}
// DefaultMockConfig returns a default mock configuration.
func DefaultMockConfig() *MockConfig {
return &MockConfig{
GitHubClient: true,
GitHubResponses: MockGitHubResponses(),
ColoredOutput: true,
FileSystem: false,
Environment: make(map[string]string),
TempDir: true,
}
}
// Advanced test environment and helper functions (consolidated from helpers.go)
// EnvironmentConfig configures a test environment.
type EnvironmentConfig struct {
ActionFixtures []string
ConfigFixture string
WithMocks bool
ExtraFiles map[string]string
}
// TestEnvironment represents a complete test environment.
type TestEnvironment struct {
TempDir string
ActionPaths []string
ConfigPath string
FixtureManager *FixtureManager
Mocks *MockSuite
Cleanup []func() error
}
// CreateTemporaryAction creates a temporary action file for testing.
func CreateTemporaryAction(t *testing.T, fixture string) string {
t.Helper()
// Load the fixture
actionFixture, err := LoadActionFixture(fixture)
if err != nil {
t.Fatalf("failed to load action fixture %s: %v", fixture, err)
}
// Create temporary directory
tempDir, cleanup := TempDir(t)
t.Cleanup(cleanup)
// Write action file
actionPath := filepath.Join(tempDir, "action.yml")
WriteTestFile(t, actionPath, actionFixture.Content)
return actionPath
}
// CreateTemporaryActionDir creates a temporary directory with an action file.
func CreateTemporaryActionDir(t *testing.T, fixture string) string {
t.Helper()
// Load the fixture
actionFixture, err := LoadActionFixture(fixture)
if err != nil {
t.Fatalf("failed to load action fixture %s: %v", fixture, err)
}
// Create temporary directory
tempDir, cleanup := TempDir(t)
t.Cleanup(cleanup)
// Write action file
actionPath := filepath.Join(tempDir, "action.yml")
WriteTestFile(t, actionPath, actionFixture.Content)
return tempDir
}
// CreateTestEnvironment sets up a complete test environment.
func CreateTestEnvironment(t *testing.T, config *EnvironmentConfig) *TestEnvironment {
t.Helper()
if config == nil {
config = &EnvironmentConfig{}
}
env := &TestEnvironment{
TempDir: "",
ActionPaths: make([]string, 0),
ConfigPath: "",
FixtureManager: GetFixtureManager(),
Cleanup: make([]func() error, 0),
}
// Create temporary directory
tempDir, cleanup := TempDir(t)
env.TempDir = tempDir
env.Cleanup = append(env.Cleanup, func() error {
cleanup()
return nil
})
// Create action files from fixtures
for _, fixture := range config.ActionFixtures {
actionPath := CreateTemporaryAction(t, fixture)
env.ActionPaths = append(env.ActionPaths, actionPath)
}
// Create config file if specified
if config.ConfigFixture != "" {
configFixture, err := LoadConfigFixture(config.ConfigFixture)
if err != nil {
t.Fatalf("failed to load config fixture %s: %v", config.ConfigFixture, err)
}
configPath := filepath.Join(tempDir, "config.yml")
WriteTestFile(t, configPath, configFixture.Content)
env.ConfigPath = configPath
}
// Set up mocks if requested
if config.WithMocks {
env.Mocks = CreateMockSuite(DefaultMockConfig())
}
return env
}
// CreateMockSuite creates a complete mock environment (enhanced from helpers.go).
func CreateMockSuite(config *MockConfig) *MockSuite {
if config == nil {
config = DefaultMockConfig()
}
suite := &MockSuite{
Environment: make(map[string]string),
TempDirs: make([]string, 0),
}
// Set up GitHub client mock
if config.GitHubClient {
responses := config.GitHubResponses
if responses == nil {
responses = MockGitHubResponses()
}
suite.GitHubClient = MockGitHubClient(responses)
}
// Set up colored output mock
if config.ColoredOutput {
suite.ColoredOutput = &MockColoredOutput{
Messages: make([]string, 0),
}
}
// Set up HTTP client mock
if config.GitHubClient {
suite.HTTPClient = &MockHTTPClient{
Responses: make(map[string]*http.Response),
Requests: make([]*http.Request, 0),
}
}
// Set up environment variables
for key, value := range config.Environment {
suite.Environment[key] = value
}
return suite
}
// SetupGitHubMocks configures GitHub API mocks for test scenarios.
func SetupGitHubMocks(scenarios []string) map[string]string {
responses := MockGitHubResponses()
// Add scenario-specific responses
for _, scenario := range scenarios {
switch scenario {
case "rate-limit":
responses["GET https://api.github.com/rate_limit"] = GitHubRateLimitResponse
case "not-found":
responses["GET https://api.github.com/repos/nonexistent/repo"] = GitHubErrorResponse
case "latest-release":
responses["GET https://api.github.com/repos/actions/checkout/releases/latest"] = GitHubReleaseResponse
}
}
return responses
}
// ValidateActionFixture validates that a fixture contains expected content.
func ValidateActionFixture(t *testing.T, fixture *ActionFixture) {
t.Helper()
if fixture == nil {
t.Fatal("fixture is nil")
}
if fixture.Content == "" {
t.Error("fixture content is empty")
}
if fixture.Name == "" {
t.Error("fixture name is empty")
}
// Basic YAML validation is done in the fixture loader
if !fixture.IsValid && fixture.Scenario != nil && fixture.Scenario.ExpectValid {
t.Errorf("fixture %s should be valid according to scenario but validation failed", fixture.Name)
}
}
// TestAllThemes runs a test with all available themes.
func TestAllThemes(t *testing.T, testFunc func(*testing.T, string)) {
t.Helper()
themes := []string{"default", "github", "minimal", "professional"}
for _, theme := range themes {
theme := theme // capture loop variable
t.Run("theme_"+theme, func(t *testing.T) {
t.Parallel()
testFunc(t, theme)
})
}
}
// TestAllFormats runs a test with all available output formats.
func TestAllFormats(t *testing.T, testFunc func(*testing.T, string)) {
t.Helper()
formats := []string{"md", "html", "json", "asciidoc"}
for _, format := range formats {
format := format // capture loop variable
t.Run("format_"+format, func(t *testing.T) {
t.Parallel()
testFunc(t, format)
})
}
}
// TestValidationScenarios runs validation tests for all invalid fixtures.
func TestValidationScenarios(t *testing.T, validatorFunc func(*testing.T, string) error) {
t.Helper()
invalidFixtures := GetInvalidFixtures()
for _, fixture := range invalidFixtures {
fixture := fixture // capture loop variable
t.Run("invalid_"+strings.ReplaceAll(fixture, "/", "_"), func(t *testing.T) {
t.Parallel()
err := validatorFunc(t, fixture)
if err == nil {
t.Errorf("expected validation error for fixture %s, but got none", fixture)
}
})
}
}
// CreateGitHubMockSuite creates a GitHub API mock suite.
func CreateGitHubMockSuite(scenarios []string) *MockSuite {
config := &MockConfig{
GitHubClient: true,
GitHubResponses: SetupGitHubMocks(scenarios),
ColoredOutput: true,
Environment: make(map[string]string),
TempDir: true,
}
return CreateMockSuite(config)
}
// AssertFixtureValid asserts that a fixture should be valid.
func AssertFixtureValid(t *testing.T, fixtureName string) {
t.Helper()
fixture, err := LoadActionFixture(fixtureName)
AssertNoError(t, err)
if !fixture.IsValid {
t.Errorf("fixture %s should be valid but failed validation", fixtureName)
}
ValidateActionFixture(t, fixture)
}
// AssertFixtureInvalid asserts that a fixture should be invalid.
func AssertFixtureInvalid(t *testing.T, fixtureName string) {
t.Helper()
fixture, err := LoadActionFixture(fixtureName)
// The fixture might load but be marked as invalid
if err == nil && fixture.IsValid {
t.Errorf("fixture %s should be invalid but passed validation", fixtureName)
}
}
// CreateActionTestCases creates test cases for action-related testing.
func CreateActionTestCases() []ActionTestCase {
validFixtures := GetValidFixtures()
invalidFixtures := GetInvalidFixtures()
cases := make([]ActionTestCase, 0, len(validFixtures)+len(invalidFixtures))
// Add valid fixture test cases
for _, fixture := range validFixtures {
actionFixture, err := LoadActionFixture(fixture)
if err != nil {
continue // Skip fixtures that can't be loaded
}
cases = append(cases, ActionTestCase{
TestCase: TestCase{
Name: "valid_" + strings.ReplaceAll(fixture, "/", "_"),
Description: "Test valid action fixture: " + fixture,
Fixture: fixture,
Config: DefaultTestConfig(),
Mocks: DefaultMockConfig(),
Expected: &ExpectedResult{
ShouldSucceed: true,
ShouldFail: false,
},
},
ActionType: actionFixture.ActionType,
ExpectValid: true,
ExpectAnalysis: true,
})
}
// Add invalid fixture test cases
for _, fixture := range invalidFixtures {
actionFixture, _ := LoadActionFixture(fixture) // Might fail, that's ok
actionType := ActionTypeInvalid
if actionFixture != nil {
actionType = actionFixture.ActionType
}
cases = append(cases, ActionTestCase{
TestCase: TestCase{
Name: "invalid_" + strings.ReplaceAll(fixture, "/", "_"),
Description: "Test invalid action fixture: " + fixture,
Fixture: fixture,
Config: DefaultTestConfig(),
Mocks: DefaultMockConfig(),
Expected: &ExpectedResult{
ShouldSucceed: false,
ShouldFail: true,
},
},
ActionType: actionType,
ExpectValid: false,
ExpectAnalysis: false,
})
}
return cases
}
// getExpectedFilename returns the expected filename for a given output format.
func getExpectedFilename(outputFormat string) string {
switch outputFormat {
case "md":
return "README.md"
case "html":
// 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"
default:
return "README.md"
}
}
// CreateGeneratorTestCases creates test cases for generator testing.
func CreateGeneratorTestCases() []GeneratorTestCase {
validFixtures := GetValidFixtures()
themes := []string{"default", "github", "minimal", "professional"}
formats := []string{"md", "html", "json", "asciidoc"}
cases := make([]GeneratorTestCase, 0)
// Create test cases for each valid fixture with each theme/format combination
for _, fixture := range validFixtures {
for _, theme := range themes {
for _, format := range formats {
expectedFilename := getExpectedFilename(format)
cases = append(cases, GeneratorTestCase{
TestCase: TestCase{
Name: fmt.Sprintf("%s_%s_%s", strings.ReplaceAll(fixture, "/", "_"), theme, format),
Description: fmt.Sprintf(
"Generate %s documentation for %s using %s theme",
format,
fixture,
theme,
),
Fixture: fixture,
Config: &TestConfig{
Theme: theme,
OutputFormat: format,
OutputDir: ".",
Verbose: false,
Quiet: false,
},
Mocks: DefaultMockConfig(),
Expected: &ExpectedResult{
ShouldSucceed: true,
ExpectedFiles: []string{expectedFilename},
},
},
Theme: theme,
OutputFormat: format,
ExpectFiles: []string{expectedFilename},
})
}
}
}
return cases
}
// CreateValidationTestCases creates test cases for validation testing.
func CreateValidationTestCases() []ValidationTestCase {
fm := GetFixtureManager()
cases := make([]ValidationTestCase, 0)
// Add test cases for all scenarios
for _, scenario := range fm.scenarios {
cases = append(cases, ValidationTestCase{
TestCase: TestCase{
Name: "validate_" + scenario.ID,
Description: scenario.Description,
Fixture: scenario.Fixture,
Config: DefaultTestConfig(),
Expected: &ExpectedResult{
ShouldSucceed: scenario.ExpectValid,
ShouldFail: !scenario.ExpectValid,
},
},
ExpectValid: scenario.ExpectValid,
ValidationLevel: "standard",
})
}
return cases
}