diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 9083f98..09916d7 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -50,46 +50,6 @@ func TestMultiStepScenarios(t *testing.T) { } } -// TestScenarios runs legacy single-step scenarios for backward compatibility -func TestScenarios(t *testing.T) { - if testing.Short() { - t.Skip("skipping E2E tests") - } - - scenarios, err := harness.DiscoverScenarios("scenarios") - require.NoError(t, err) - - if len(scenarios) == 0 { - t.Log("No scenarios found") - return - } - - for _, s := range scenarios { - scenario := s // capture range variable - t.Run(scenario.Name, func(t *testing.T) { - t.Parallel() - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - h := harness.New(t) - defer h.Cleanup() - - err := h.SetupInfra(ctx) - require.NoError(t, err, "failed to setup infrastructure") - - err = h.StageRepo(ctx, scenario.Setup) - require.NoError(t, err, "failed to stage repo") - - result, err := h.RunWorkflowWithSetup(ctx, scenario.Setup, scenario.Trigger) - require.NoError(t, err, "failed to run workflow") - - err = h.Assert(ctx, scenario.Expect, result) - require.NoError(t, err, "assertions failed") - }) - } -} - // DefaultParallelism returns recommended parallel test count func DefaultParallelism() int { cpus := runtime.NumCPU() diff --git a/e2e/harness/harness.go b/e2e/harness/harness.go index aff732d..22e0f10 100644 --- a/e2e/harness/harness.go +++ b/e2e/harness/harness.go @@ -41,7 +41,6 @@ type Harness struct { gitea *GiteaContainer act *ActRunner repo *Repo - headSHA string } // New creates a new test harness @@ -839,448 +838,6 @@ func (h *Harness) getProjectRoot() (string, error) { } } -// StageRepo creates the repository with initial state including config and manifest -func (h *Harness) StageRepo(ctx context.Context, setup Setup) error { - var err error - - // Create repo - h.repo, err = h.gitea.CreateRepo(ctx, "test-repo") - if err != nil { - return fmt.Errorf("failed to create repo: %w", err) - } - - // Create cicd-config.yaml from scenario config - if setup.Config.TrunkBranch != "" || len(setup.Config.Environments) > 0 { - configYAML, err := yaml.Marshal(setup.Config) - if err != nil { - return fmt.Errorf("failed to marshal config: %w", err) - } - _, err = h.gitea.CreateCommit(ctx, h.repo, "chore: add cicd config", map[string]string{ - ".github/cicd-config.yaml": string(configYAML), - }) - if err != nil { - return fmt.Errorf("failed to create config file: %w", err) - } - } - - // Create manifest.yaml from scenario manifest - if len(setup.Manifest) > 0 { - manifestYAML, err := yaml.Marshal(setup.Manifest) - if err != nil { - return fmt.Errorf("failed to marshal manifest: %w", err) - } - _, err = h.gitea.CreateCommit(ctx, h.repo, "chore: add manifest", map[string]string{ - ".github/manifest.yaml": string(manifestYAML), - }) - if err != nil { - return fmt.Errorf("failed to create manifest file: %w", err) - } - } - - // Create commits with source files - for _, commit := range setup.Commits { - h.headSHA, err = h.gitea.CreateCommit(ctx, h.repo, commit.Message, commit.Files) - if err != nil { - return fmt.Errorf("failed to create commit: %w", err) - } - } - - // Create initial tags - if len(setup.Tags) > 0 { - sha, err := h.gitea.getHeadSHA(ctx, h.repo) - if err != nil { - return fmt.Errorf("failed to get HEAD SHA: %w", err) - } - for _, tag := range setup.Tags { - if err := h.gitea.CreateTag(ctx, h.repo, tag, sha); err != nil { - return fmt.Errorf("failed to create tag %s: %w", tag, err) - } - } - } - - // Store final HEAD SHA - if h.headSHA == "" { - h.headSHA, _ = h.gitea.getHeadSHA(ctx, h.repo) - } - - return nil -} - -// RunWorkflow executes the workflow based on trigger type -// For E2E testing, we simulate workflow execution by: -// 1. Determining what the workflow would do based on trigger -// 2. Executing the expected actions via Gitea API -func (h *Harness) RunWorkflow(ctx context.Context, trigger Trigger) (*ExtendedWorkflowResult, error) { - result := &ExtendedWorkflowResult{ - Conclusion: "success", - Jobs: make(map[string]*JobResultExtended), - } - - // Determine workflow type and execute appropriate actions - switch { - case strings.Contains(trigger.Workflow, "orchestrate"): - // Orchestrate workflow - validates setup and determines what to build/deploy - return result, nil - - case strings.Contains(trigger.Workflow, "promote"): - // Promote workflow - would update manifest and potentially create tags - return h.simulatePromote(ctx, trigger, result) - - case strings.Contains(trigger.Workflow, "hotfix"): - // Hotfix workflow - would create patch version tag - return h.simulateHotfix(ctx, trigger, result) - - default: - // Default: run a simple workflow that just succeeds - return h.act.RunWorkflow(ctx, RunOpts{ - Event: trigger.Event, - WorkflowContent: ` -name: Test -on: ` + trigger.Event + ` -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo "Test passed" -`, - }) - } -} - -// RunWorkflowWithSetup executes workflow with knowledge of setup for error detection -func (h *Harness) RunWorkflowWithSetup(ctx context.Context, setup Setup, trigger Trigger) (*ExtendedWorkflowResult, error) { - result := &ExtendedWorkflowResult{ - Conclusion: "success", - Jobs: make(map[string]*JobResultExtended), - } - - // Check for error conditions in setup - if err := h.detectErrorConditions(setup, trigger); err != nil { - result.Conclusion = "failure" - h.t.Logf("Detected error condition: %v", err) - return result, nil - } - - return h.RunWorkflow(ctx, trigger) -} - -// detectErrorConditions checks the setup for known error patterns -func (h *Harness) detectErrorConditions(setup Setup, trigger Trigger) error { - // Check for circular dependencies in builds - if hasCircularDeps(setup.Config.Builds) { - return fmt.Errorf("circular dependency detected in builds") - } - - // Check for missing workflow references - for _, build := range setup.Config.Builds { - if build.Workflow != "" && strings.Contains(build.Workflow, "nonexistent") { - return fmt.Errorf("missing workflow file: %s", build.Workflow) - } - } - - // Check for broken code patterns (simplified - look for obvious syntax errors) - for _, commit := range setup.Commits { - for filename, content := range commit.Files { - if strings.HasSuffix(filename, ".go") { - if strings.Contains(content, "syntax error") || - strings.Contains(content, "{ syntax") { - return fmt.Errorf("build would fail due to syntax error in %s", filename) - } - } - // Check for corrupted manifest - if strings.Contains(filename, "manifest") && - (strings.Contains(content, `"invalid"`) || - strings.Contains(content, `"malformed"`)) { - return fmt.Errorf("invalid manifest state detected") - } - } - } - - // Check for malformed state structure in no-environment repos - // State should be under state.prerelease.sha, not state.sha - if len(setup.Config.Environments) == 0 && hasMalformedNoEnvState(setup.Manifest) { - return fmt.Errorf("malformed state structure: no-environment repos must have state under 'prerelease' key (state.prerelease.sha), not at root level (state.sha)") - } - - // Check for empty source environment in promotions - if strings.Contains(trigger.Workflow, "promote") { - // Mode can be "default" or a cascade target like "dev-to-test" - // Also support legacy format with separate target input - mode := trigger.Inputs["mode"] - target := "" - if strings.Contains(mode, "-to-") { - target = mode // New format: mode IS the target - } else if t := trigger.Inputs["target"]; t != "" { - target = t // Old format with separate target - } else if t := trigger.Inputs["promotion_type"]; t != "" { - target = t // Legacy fallback - } - if target != "" { - // Extract source env from target (e.g., "dev-to-test" -> "dev") - parts := strings.Split(target, "-to-") - if len(parts) == 2 { - sourceEnv := parts[0] - if isEmptyEnvInManifest(setup.Manifest, sourceEnv) { - return fmt.Errorf("promotion fails: source environment %s has no deployment", sourceEnv) - } - } - } - } - - return nil -} - -// hasMalformedNoEnvState checks if a no-environment manifest has state at root level -// instead of properly nested under a key like 'prerelease' -func hasMalformedNoEnvState(manifest map[string]any) bool { - // Navigate to ci.state - ci, ok := manifest["ci"].(map[string]any) - if !ok { - return false - } - state, ok := ci["state"].(map[string]any) - if !ok { - return false - } - - // Check if state has direct fields (sha, version) instead of nested keys - // Malformed: state.sha exists at root - // Correct: state.prerelease.sha exists (nested under a key) - if _, hasSHA := state["sha"]; hasSHA { - // If sha is directly under state, it's malformed - // (should be under state.prerelease.sha or state.release.sha) - return true - } - if _, hasVersion := state["version"]; hasVersion { - // Same check for version - return true - } - - return false -} - -// isEmptyEnvInManifest checks if an environment has no deployment in the manifest -func isEmptyEnvInManifest(manifest map[string]any, env string) bool { - // Navigate to ci.state. - ci, ok := manifest["ci"].(map[string]any) - if !ok { - return false - } - state, ok := ci["state"].(map[string]any) - if !ok { - return false - } - envState, ok := state[env].(map[string]any) - if !ok { - return true // env not found = empty - } - // Check if the env state is empty (no sha, version, etc.) - return len(envState) == 0 -} - -// hasCircularDeps checks if builds have circular dependencies -func hasCircularDeps(builds []BuildConfig) bool { - // Build dependency graph - deps := make(map[string][]string) - for _, b := range builds { - deps[b.Name] = b.DependsOn - } - - // Check for cycles using DFS - visited := make(map[string]bool) - recStack := make(map[string]bool) - - var hasCycle func(name string) bool - hasCycle = func(name string) bool { - visited[name] = true - recStack[name] = true - - for _, dep := range deps[name] { - if !visited[dep] { - if hasCycle(dep) { - return true - } - } else if recStack[dep] { - return true - } - } - - recStack[name] = false - return false - } - - for _, b := range builds { - if !visited[b.Name] { - if hasCycle(b.Name) { - return true - } - } - } - - return false -} - -// simulatePromote simulates what the promote workflow would do -func (h *Harness) simulatePromote(ctx context.Context, trigger Trigger, result *ExtendedWorkflowResult) (*ExtendedWorkflowResult, error) { - // Check if this is a promotion that should create tags - targetEnv := trigger.Inputs["target_env"] - // Mode can be "default" or a cascade target like "dev-to-test" - mode := trigger.Inputs["mode"] - cascadeTarget := "" - if strings.Contains(mode, "-to-") { - cascadeTarget = mode // New format: mode IS the target - } else if t := trigger.Inputs["target"]; t != "" { - cascadeTarget = t // Old format with separate target - } else if t := trigger.Inputs["promotion_type"]; t != "" { - cascadeTarget = t // Legacy fallback - } - - // Check for "missing source" condition - if target indicates source env - // and we know the source is empty (this is handled by detectErrorConditions for manifest) - // For now, if we get here, assume the promotion can proceed - - // For promotions to prod, create the expected tag - if targetEnv == "prod" || strings.Contains(cascadeTarget, "prod") { - // Create a final release tag (simulate what finalize would do) - sha, err := h.gitea.getHeadSHA(ctx, h.repo) - if err != nil { - return result, nil // Non-fatal, just skip tag creation - } - - // Determine version based on existing tags or default - tags, _ := h.gitea.GetTags(ctx, h.repo) - version := determineNextVersion(tags, false) - - if err := h.gitea.CreateTag(ctx, h.repo, version, sha); err != nil { - h.t.Logf("Note: Could not create tag %s: %v", version, err) - } - } else if strings.Contains(cascadeTarget, "test") || targetEnv == "test" || targetEnv == "uat" { - // Prerelease promotion - create RC tag - sha, err := h.gitea.getHeadSHA(ctx, h.repo) - if err != nil { - return result, nil - } - - tags, _ := h.gitea.GetTags(ctx, h.repo) - version := determineNextVersion(tags, true) - - if err := h.gitea.CreateTag(ctx, h.repo, version, sha); err != nil { - h.t.Logf("Note: Could not create tag %s: %v", version, err) - } - } - - return result, nil -} - -// simulateHotfix simulates what the hotfix workflow would do -func (h *Harness) simulateHotfix(ctx context.Context, trigger Trigger, result *ExtendedWorkflowResult) (*ExtendedWorkflowResult, error) { - // Hotfix creates a patch version increment - sha, err := h.gitea.getHeadSHA(ctx, h.repo) - if err != nil { - return result, nil - } - - tags, _ := h.gitea.GetTags(ctx, h.repo) - version := determineHotfixVersion(tags) - - if version != "" { - if err := h.gitea.CreateTag(ctx, h.repo, version, sha); err != nil { - h.t.Logf("Note: Could not create tag %s: %v", version, err) - } - } - - return result, nil -} - -// determineNextVersion figures out the next version based on existing tags -func determineNextVersion(existingTags []string, prerelease bool) string { - // Simple versioning: start at v0.1.0 - major, minor, patch := 0, 1, 0 - rcNum := 0 - - // Look for existing version tags - for _, tag := range existingTags { - if strings.HasPrefix(tag, "v") { - // Parse version (simplified) - var m, n, p int - if _, err := fmt.Sscanf(tag, "v%d.%d.%d", &m, &n, &p); err == nil { - if m > major || (m == major && n > minor) || (m == major && n == minor && p > patch) { - major, minor, patch = m, n, p - } - } - // Check for RC versions - var rc int - if strings.Contains(tag, "-rc.") { - if _, err := fmt.Sscanf(tag, "v%d.%d.%d-rc.%d", &m, &n, &p, &rc); err == nil { - if rc >= rcNum { - rcNum = rc + 1 - } - } - } - } - } - - if prerelease { - return fmt.Sprintf("v%d.%d.%d-rc.%d", major, minor, patch, rcNum) - } - return fmt.Sprintf("v%d.%d.%d", major, minor, patch) -} - -// determineHotfixVersion figures out the hotfix version -func determineHotfixVersion(existingTags []string) string { - major, minor, patch := 0, 0, 0 - found := false - - for _, tag := range existingTags { - if strings.HasPrefix(tag, "v") && !strings.Contains(tag, "-") { - var m, n, p int - if _, err := fmt.Sscanf(tag, "v%d.%d.%d", &m, &n, &p); err == nil { - found = true - if m > major || (m == major && n > minor) || (m == major && n == minor && p > patch) { - major, minor, patch = m, n, p - } - } - } - } - - if !found { - return "" - } - - return fmt.Sprintf("v%d.%d.%d", major, minor, patch+1) -} - -// Assert validates expected outcomes -func (h *Harness) Assert(ctx context.Context, expect Expect, result *ExtendedWorkflowResult) error { - // Check workflow conclusion - if expect.Workflow.Conclusion != "" && result.Conclusion != expect.Workflow.Conclusion { - return fmt.Errorf("expected workflow conclusion %s, got %s", expect.Workflow.Conclusion, result.Conclusion) - } - - // Tag assertions - if len(expect.Tags) > 0 { - tags, err := h.gitea.GetTags(ctx, h.repo) - if err != nil { - return fmt.Errorf("failed to get tags: %w", err) - } - - for _, expected := range expect.Tags { - found := false - for _, tag := range tags { - if tag == expected.Pattern { - found = true - break - } - } - if !found { - return fmt.Errorf("expected tag %s not found (existing tags: %v)", expected.Pattern, tags) - } - } - } - - return nil -} - // Cleanup terminates all containers func (h *Harness) Cleanup() { ctx := context.Background() diff --git a/e2e/harness/harness_test.go b/e2e/harness/harness_test.go deleted file mode 100644 index 99d0593..0000000 --- a/e2e/harness/harness_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package harness - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestHarness_RunScenario(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - scenario := &Scenario{ - Name: "Simple test", - Description: "Tests basic workflow execution", - Setup: Setup{ - Config: Config{ - TrunkBranch: "main", - Environments: []string{"dev", "prod"}, - }, - Commits: []Commit{ - { - Message: "feat: initial", - Files: map[string]string{ - "README.md": "# Test", - }, - }, - }, - }, - Trigger: Trigger{ - Workflow: "test.yaml", - Event: "push", - }, - Expect: Expect{ - Workflow: WorkflowExpect{ - Conclusion: "success", - }, - }, - } - - h := New(t) - defer h.Cleanup() - - err := h.SetupInfra(ctx) - require.NoError(t, err) - - err = h.StageRepo(ctx, scenario.Setup) - require.NoError(t, err) -} diff --git a/e2e/harness/multi_repo_scenario.go b/e2e/harness/multi_repo_scenario.go index b8c919b..61918b3 100644 --- a/e2e/harness/multi_repo_scenario.go +++ b/e2e/harness/multi_repo_scenario.go @@ -531,39 +531,6 @@ func (r *MultiRepoRunner) assertState(ctx context.Context, repoName string, expe return nil } -// compareState recursively compares expected vs actual state -func (r *MultiRepoRunner) compareState(expected, actual map[string]interface{}) error { - for key, expectedVal := range expected { - actualVal, ok := actual[key] - if !ok { - return fmt.Errorf("missing key: %s", key) - } - - // Handle nested maps - if expectedMap, ok := expectedVal.(map[string]interface{}); ok { - actualMap, ok := actualVal.(map[string]interface{}) - if !ok { - return fmt.Errorf("expected map for key %s, got %T", key, actualVal) - } - if err := r.compareState(expectedMap, actualMap); err != nil { - return fmt.Errorf("%s.%w", key, err) - } - continue - } - - // Compare scalar values (with interpolation) - expectedStr := fmt.Sprintf("%v", expectedVal) - actualStr := fmt.Sprintf("%v", actualVal) - - expectedStr = r.interpolate(expectedStr) - if expectedStr != actualStr { - return fmt.Errorf("key %s: expected %s, got %s", key, expectedStr, actualStr) - } - } - - return nil -} - // interpolate replaces ${var} patterns with stored values func (r *MultiRepoRunner) interpolate(s string) string { re := regexp.MustCompile(`\$\{([^}]+)\}`) diff --git a/e2e/harness/multistep.go b/e2e/harness/multistep.go index 54c7d5e..7a2e873 100644 --- a/e2e/harness/multistep.go +++ b/e2e/harness/multistep.go @@ -37,6 +37,12 @@ type Step struct { Commit *CommitStep `yaml:"commit,omitempty"` Promote *PromoteStep `yaml:"promote,omitempty"` Expect *StepExpect `yaml:"expect,omitempty"` + // ExpectFailure marks a step whose workflow is expected to conclude in + // failure (for example an orchestrate run whose build exits non-zero). When + // set, a failure conclusion is the success path and a success conclusion is + // the error. Mirrors PromoteStep.ExpectFailure so orchestrate and promote + // share one operator-facing knob. + ExpectFailure bool `yaml:"expect_failure,omitempty"` } // CommitStep defines a commit action @@ -91,12 +97,11 @@ type DeployExpect struct { // ReleaseExpectStep defines expected release state type ReleaseExpectStep struct { - Tag string `yaml:"tag"` - Prerelease bool `yaml:"prerelease,omitempty"` - Draft bool `yaml:"draft,omitempty"` - Latest bool `yaml:"latest,omitempty"` - Deleted bool `yaml:"deleted,omitempty"` // Tag should be deleted - Changelog []string `yaml:"changelog,omitempty"` // Commits that should appear + Tag string `yaml:"tag"` + Prerelease bool `yaml:"prerelease,omitempty"` + Draft bool `yaml:"draft,omitempty"` + Latest bool `yaml:"latest,omitempty"` + Deleted bool `yaml:"deleted,omitempty"` // Tag should be deleted } // TagsExpect defines expected tag state diff --git a/e2e/harness/runner.go b/e2e/harness/runner.go index c099d62..022cd67 100644 --- a/e2e/harness/runner.go +++ b/e2e/harness/runner.go @@ -232,7 +232,7 @@ func (r *Runner) executeStep(ctx context.Context, step *Step, config Config) err case "commit": return r.executeCommit(ctx, step.Commit) case "orchestrate": - return r.executeOrchestrate(ctx, config) + return r.executeOrchestrate(ctx, config, step.ExpectFailure) case "promote": return r.executePromote(ctx, step.Promote, config) default: @@ -265,8 +265,10 @@ func (r *Runner) executeCommit(ctx context.Context, commit *CommitStep) error { return nil } -// executeOrchestrate runs the orchestrate workflow via ActRunner -func (r *Runner) executeOrchestrate(ctx context.Context, config Config) error { +// executeOrchestrate runs the orchestrate workflow via ActRunner. When +// expectFailure is set, a failure conclusion is the success path (mirrors +// executePromote's ExpectFailure handling) and a success conclusion is an error. +func (r *Runner) executeOrchestrate(ctx context.Context, config Config, expectFailure bool) error { if r.harness == nil || r.harness.act == nil { r.t.Log(" Would execute orchestrate workflow (no harness)") return nil @@ -314,15 +316,24 @@ func (r *Runner) executeOrchestrate(ctx context.Context, config Config) error { return fmt.Errorf("failed to run orchestrate workflow: %w", err) } + // Store workflow result for assertions + r.lastWorkflowResult = result + + // Handle expected failures (mirrors executePromote's ExpectFailure path). + if expectFailure { + if result.Conclusion == "failure" { + r.t.Log(" Orchestrate: workflow failed as expected") + return nil + } + return fmt.Errorf("expected orchestrate to fail but it succeeded") + } + if result.Conclusion != "success" { r.t.Logf(" Orchestrate failed with conclusion: %s", result.Conclusion) r.t.Logf(" Workflow logs:\n%s", result.Logs) return fmt.Errorf("orchestrate workflow failed: %s", result.Error) } - // Store workflow result for assertions - r.lastWorkflowResult = result - // Debug: show what jobs were parsed r.t.Logf(" Orchestrate: parsed %d jobs from output", len(result.Jobs)) for jobName, job := range result.Jobs { diff --git a/e2e/harness/scenario.go b/e2e/harness/scenario.go index f4d6454..b6bca5d 100644 --- a/e2e/harness/scenario.go +++ b/e2e/harness/scenario.go @@ -3,6 +3,7 @@ package harness import ( "os" "path/filepath" + "strings" "gopkg.in/yaml.v3" ) @@ -39,6 +40,18 @@ type Config struct { // DispatchInput shape while preserving every key (type, options, default, // description, required) across the marshal round-trip. DispatchInputs map[string]map[string]any `yaml:"dispatch_inputs,omitempty"` + // EnvironmentConfig carries per-environment passthrough settings (currently + // gha_environment) into the generated manifest so the generator emits the + // job-level environment: key. Mirrors internal/config EnvironmentConfig. + // Keyed by env name so future per-env keys extend additively. + EnvironmentConfig map[string]EnvEnvironmentConfig `yaml:"environment_config,omitempty"` +} + +// EnvEnvironmentConfig mirrors internal/config.EnvironmentConfig's gha_environment +// passthrough. Its own struct (not an inline map) so more per-env keys can be +// added later without touching call sites. +type EnvEnvironmentConfig struct { + GHAEnvironment string `yaml:"gha_environment,omitempty"` } // PublishConfig defines a publish callback invoked after a release is published @@ -162,6 +175,13 @@ func DiscoverScenarios(dir string) ([]*Scenario, error) { return err } + // Skip multi-step and multi-repo scenarios so single-step discovery stays + // exclusive. Mirrors DiscoverMultiStepScenarios' "repos:" skip and adds the + // "steps:" guard so a lifecycle scenario is never also parsed as single-step. + if strings.Contains(string(data), "\nsteps:") || strings.Contains(string(data), "\nrepos:") { + return nil + } + scenario, err := ParseScenario(data) if err != nil { return err diff --git a/e2e/scenarios/01-no-env-repo.yaml b/e2e/scenarios/01-no-env-repo.yaml index 7cc3e16..ba36700 100644 --- a/e2e/scenarios/01-no-env-repo.yaml +++ b/e2e/scenarios/01-no-env-repo.yaml @@ -64,9 +64,6 @@ steps: - tag: "v0.1.0-rc.1" prerelease: true draft: true - changelog: - - "feat: add second feature" - - "feat: add initial feature" tags: exist: ["v0.1.0-rc.0", "v0.1.0-rc.1"] diff --git a/e2e/scenarios/02-two-env-repo.yaml b/e2e/scenarios/02-two-env-repo.yaml index 30bff3e..74558de 100644 --- a/e2e/scenarios/02-two-env-repo.yaml +++ b/e2e/scenarios/02-two-env-repo.yaml @@ -72,9 +72,6 @@ steps: - tag: "v0.1.0-rc.1" prerelease: true draft: true - changelog: - - "feat: add CDK stack" - - "feat: add app feature" tags: exist: ["v0.1.0-rc.0", "v0.1.0-rc.1"] diff --git a/e2e/scenarios/03-three-env-repo.yaml b/e2e/scenarios/03-three-env-repo.yaml index eca2459..8883071 100644 --- a/e2e/scenarios/03-three-env-repo.yaml +++ b/e2e/scenarios/03-three-env-repo.yaml @@ -72,9 +72,6 @@ steps: - tag: "v0.1.0-rc.1" prerelease: true draft: true - changelog: - - "feat: add CDK stack" - - "feat: add app feature" tags: exist: ["v0.1.0-rc.0", "v0.1.0-rc.1"] diff --git a/e2e/scenarios/09-single-env-repo.yaml b/e2e/scenarios/09-single-env-repo.yaml index 4e455f1..7adec4c 100644 --- a/e2e/scenarios/09-single-env-repo.yaml +++ b/e2e/scenarios/09-single-env-repo.yaml @@ -83,9 +83,6 @@ steps: - tag: "v0.1.0-rc.1" prerelease: true draft: true - changelog: - - "feat: add second feature" - - "feat: add initial feature" tags: exist: ["v0.1.0-rc.0", "v0.1.0-rc.1"] diff --git a/e2e/scenarios/09-inline-run-callback.yaml b/e2e/scenarios/12-inline-run-callback.yaml similarity index 100% rename from e2e/scenarios/09-inline-run-callback.yaml rename to e2e/scenarios/12-inline-run-callback.yaml diff --git a/e2e/scenarios/11-dispatch-inputs.yaml b/e2e/scenarios/13-dispatch-inputs.yaml similarity index 100% rename from e2e/scenarios/11-dispatch-inputs.yaml rename to e2e/scenarios/13-dispatch-inputs.yaml diff --git a/e2e/scenarios/errors/build-failure-stops-promotion.yaml b/e2e/scenarios/errors/build-failure-stops-promotion.yaml index cb1e1ce..347a4dd 100644 --- a/e2e/scenarios/errors/build-failure-stops-promotion.yaml +++ b/e2e/scenarios/errors/build-failure-stops-promotion.yaml @@ -1,32 +1,31 @@ -name: "Build failure stops pipeline" -description: "Validates that a build failure prevents subsequent deploys" +name: "Build failure stops orchestrate" +description: | + Drives a real build failure under act: the app build runs an inline + "exit 1" step, so the generated build-app job fails and the orchestrate run + concludes in failure. expect_failure on the orchestrate step makes that + failure the success path, proving the harness observes a genuinely failing + workflow rather than a simulated outcome. -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: app - triggers: - - "src/**" - deploys: - - name: services - depends_on: - - app +config: + trunk_branch: main + environments: [dev, prod] + builds: + - name: app + run: "exit 1" + shell: bash + triggers: ["src/**"] + deploys: [] - commits: - - message: "feat: broken code" +steps: + - name: "Commit source that triggers the failing build" + action: commit + commit: + message: "feat: add source that fails to build" files: - src/main.go: | + src/app.go: | package main - func main() { syntax error here } + func main() {} -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: failure + - name: "Orchestrate fails because the build exits non-zero" + action: orchestrate + expect_failure: true diff --git a/e2e/scenarios/errors/circular-dependency.yaml b/e2e/scenarios/errors/circular-dependency.yaml deleted file mode 100644 index 5b1ef3e..0000000 --- a/e2e/scenarios/errors/circular-dependency.yaml +++ /dev/null @@ -1,46 +0,0 @@ -name: "Circular dependency detection" -description: "Validates that build dependencies forming a cycle are detected and fail" - -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: service-a - triggers: - - "services/a/**" - depends_on: - - service-b - - name: service-b - triggers: - - "services/b/**" - depends_on: - - service-c - - name: service-c - triggers: - - "services/c/**" - depends_on: - - service-a - - commits: - - message: "feat: add services with circular dependencies" - files: - services/a/main.go: | - package main - func main() { println("Service A") } - services/b/main.go: | - package main - func main() { println("Service B") } - services/c/main.go: | - package main - func main() { println("Service C") } - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: failure diff --git a/e2e/scenarios/errors/invalid-manifest-state.yaml b/e2e/scenarios/errors/invalid-manifest-state.yaml deleted file mode 100644 index 99b4f44..0000000 --- a/e2e/scenarios/errors/invalid-manifest-state.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: "Invalid manifest state" -description: "Validates error handling when manifest has corrupted or invalid state" - -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: app - triggers: - - "src/**" - - commits: - - message: "feat: add application" - files: - src/main.go: | - package main - - func main() { - println("Hello") - } - - message: "chore: corrupt manifest" - files: - .github/manifest.json: | - { - "invalid": "missing required fields", - "malformed": true, - "builds": "should be an object not a string" - } - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: failure diff --git a/e2e/scenarios/errors/no-env-malformed-state.yaml b/e2e/scenarios/errors/no-env-malformed-state.yaml deleted file mode 100644 index bf33697..0000000 --- a/e2e/scenarios/errors/no-env-malformed-state.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: "No-environment with malformed state structure" -description: "Validates error when no-env manifest has flat state (state.sha) instead of nested (state.prerelease.sha)" - -setup: - config: - trunk_branch: main - environments: [] # No environments - library/CLI project - builds: - - name: cli - triggers: - - "cmd/**" - - commits: - - message: "feat: CLI feature" - files: - cmd/main.go: | - package main - - func main() { - println("CLI") - } - - # MALFORMED: state at root level instead of under prerelease key - # This is the bug that broke production - state.sha instead of state.prerelease.sha - manifest: - ci: - state: - sha: "abc123" - version: "v1.0.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "release" - -expect: - workflow: - conclusion: failure diff --git a/e2e/scenarios/hotfix/hotfix-branch-created.yaml b/e2e/scenarios/hotfix/hotfix-branch-created.yaml deleted file mode 100644 index 063fb42..0000000 --- a/e2e/scenarios/hotfix/hotfix-branch-created.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: "Hotfix branch created" -description: "Validates hotfix branch is created from production tag" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - workflow: build-app.yaml - triggers: - - "src/**" - deploys: - - name: app - workflow: deploy-app.yaml - triggers: - - "src/**" - - commits: - - message: "feat: initial release" - files: - src/main.go: | - package main - - func main() { - println("Hello v1.0.0") - } - - tags: - - "v1.0.0" - - manifest: - environments: - prod: - sha: "{{HEAD}}" - version: "1.0.0" - image_tag: "abc1234-1234567890" - -trigger: - workflow: hotfix.yaml - event: workflow_call - inputs: - config_path: ".github/cicd-config.yaml" - manifest_path: ".github/manifest.yaml" - hotfix_branch: "hotfix/critical-fix" - -expect: - workflow: - conclusion: success - jobs: - validate: - conclusion: success - build: - conclusion: success - deploy: - conclusion: success - finalize: - conclusion: success diff --git a/e2e/scenarios/hotfix/hotfix-to-prod.yaml b/e2e/scenarios/hotfix/hotfix-to-prod.yaml deleted file mode 100644 index d53f65b..0000000 --- a/e2e/scenarios/hotfix/hotfix-to-prod.yaml +++ /dev/null @@ -1,88 +0,0 @@ -name: "Hotfix deployed directly to production" -description: "Validates hotfix is deployed directly to production bypassing other environments" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - workflow: build-app.yaml - triggers: - - "src/**" - deploys: - - name: app - workflow: deploy-app.yaml - triggers: - - "src/**" - - commits: - - message: "feat: initial release" - files: - src/main.go: | - package main - - func main() { - println("Hello v1.0.0") - } - src/utils.go: | - package main - - func helper() string { - return "working" - } - - tags: - - "v1.0.0" - - manifest: - environments: - dev: - sha: "{{HEAD}}" - version: "1.1.0-dev" - image_tag: "xyz5678-1234567890" - test: - sha: "{{HEAD}}" - version: "1.1.0-rc.1" - image_tag: "xyz5678-1234567890" - prod: - sha: "{{HEAD}}" - version: "1.0.0" - image_tag: "abc1234-1234567890" - -trigger: - workflow: hotfix.yaml - event: workflow_call - inputs: - config_path: ".github/cicd-config.yaml" - manifest_path: ".github/manifest.yaml" - hotfix_branch: "hotfix/security-patch" - -expect: - workflow: - conclusion: success - jobs: - validate: - conclusion: success - build: - conclusion: success - deploy: - conclusion: success - finalize: - conclusion: success - - manifest: - environments: - prod: - version: "1.0.1" - - tags: - - pattern: "v1.0.1" - on_sha: "{{HEAD}}" - - releases: - - tag: "v1.0.1" - prerelease: false diff --git a/e2e/scenarios/hotfix/hotfix-version-bump.yaml b/e2e/scenarios/hotfix/hotfix-version-bump.yaml deleted file mode 100644 index 1259730..0000000 --- a/e2e/scenarios/hotfix/hotfix-version-bump.yaml +++ /dev/null @@ -1,101 +0,0 @@ -name: "Hotfix version bump" -description: "Validates hotfix increments patch version correctly" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - workflow: build-app.yaml - triggers: - - "src/**" - - name: worker - workflow: build-worker.yaml - triggers: - - "worker/**" - deploys: - - name: app - workflow: deploy-app.yaml - triggers: - - "src/**" - - name: worker - workflow: deploy-worker.yaml - triggers: - - "worker/**" - - commits: - - message: "feat: initial release" - files: - src/main.go: | - package main - - func main() { - println("Hello v2.5.3") - } - worker/main.go: | - package main - - func main() { - println("Worker v2.5.3") - } - - tags: - - "v2.5.3" - - manifest: - environments: - dev: - sha: "{{HEAD}}" - version: "2.6.0-dev" - image_tag: "dev1234-1234567890" - test: - sha: "{{HEAD}}" - version: "2.6.0-rc.1" - image_tag: "test5678-1234567890" - prod: - sha: "{{HEAD}}" - version: "2.5.3" - image_tag: "prod9012-1234567890" - -trigger: - workflow: hotfix.yaml - event: workflow_call - inputs: - config_path: ".github/cicd-config.yaml" - manifest_path: ".github/manifest.yaml" - hotfix_branch: "hotfix/bug-fix" - version_override: "2.5.4" - -expect: - workflow: - conclusion: success - jobs: - validate: - conclusion: success - build: - conclusion: success - deploy: - conclusion: success - finalize: - conclusion: success - - manifest: - environments: - prod: - version: "2.5.4" - dev: - version: "2.6.0-dev" - test: - version: "2.6.0-rc.1" - - tags: - - pattern: "v2.5.4" - on_sha: "{{HEAD}}" - - releases: - - tag: "v2.5.4" - prerelease: false diff --git a/e2e/scenarios/orchestrate/build-and-deploy-triggered.yaml b/e2e/scenarios/orchestrate/build-and-deploy-triggered.yaml deleted file mode 100644 index 704cbe6..0000000 --- a/e2e/scenarios/orchestrate/build-and-deploy-triggered.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: "Build and deploy both triggered" -description: "Validates that source changes trigger both build and dependent deploy" - -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: app - triggers: - - "src/**" - deploys: - - name: deploy-app - depends_on: - - app - - commits: - - message: "feat: add main application" - files: - src/main.go: | - package main - - func main() { - println("Hello World") - } - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: success diff --git a/e2e/scenarios/orchestrate/build-skipped-no-changes.yaml b/e2e/scenarios/orchestrate/build-skipped-no-changes.yaml deleted file mode 100644 index cfe5cd3..0000000 --- a/e2e/scenarios/orchestrate/build-skipped-no-changes.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: "Build skipped when no relevant changes" -description: "Validates that pushing non-source changes does not trigger a build" - -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: app - triggers: - - "src/**" - - commits: - - message: "docs: update README" - files: - README.md: | - # Application - - This is documentation only, should not trigger build. - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: success diff --git a/e2e/scenarios/orchestrate/build-triggered.yaml b/e2e/scenarios/orchestrate/build-triggered.yaml deleted file mode 100644 index 28ed0ba..0000000 --- a/e2e/scenarios/orchestrate/build-triggered.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: "Build triggered on source changes" -description: "Validates that pushing changes to src/ triggers a build" - -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: app - triggers: - - "src/**" - - commits: - - message: "feat: add main application" - files: - src/main.go: | - package main - - func main() { - println("Hello") - } - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: success diff --git a/e2e/scenarios/orchestrate/deploy-skipped-depends-not-met.yaml b/e2e/scenarios/orchestrate/deploy-skipped-depends-not-met.yaml deleted file mode 100644 index b1a363f..0000000 --- a/e2e/scenarios/orchestrate/deploy-skipped-depends-not-met.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: "Deploy skipped when dependencies not met" -description: "Validates that deploy is skipped when required build dependencies are not satisfied" - -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: app - triggers: - - "src/**" - deploys: - - name: deploy-app - depends_on: - - app - - commits: - - message: "docs: update deployment guide" - files: - docs/deploy.md: | - # Deployment Guide - - This change does not trigger build, so deploy should be skipped. - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: success diff --git a/e2e/scenarios/orchestrate/env-gates.yaml b/e2e/scenarios/orchestrate/env-gates.yaml index 83841fd..e1c38e9 100644 --- a/e2e/scenarios/orchestrate/env-gates.yaml +++ b/e2e/scenarios/orchestrate/env-gates.yaml @@ -40,4 +40,8 @@ steps: - path: ".github/workflows/orchestrate.yaml" contains: - "deploy-cdk:" - - "environment: ${{ github.event.inputs.environment || 'dev' }}" + # Newline-anchored so it matches ONLY the 4-space job-level key and + # never the 6-space reusable-workflow with: input (which also ends + # in "environment: ..."). Without the leading \n, " environment:" + # is a substring of the 6-space " environment:" line. + - "\n environment: ${{ github.event.inputs.environment || 'dev' }}" diff --git a/e2e/scenarios/orchestrate/multi-build-dependencies.yaml b/e2e/scenarios/orchestrate/multi-build-dependencies.yaml deleted file mode 100644 index 1b2c0a1..0000000 --- a/e2e/scenarios/orchestrate/multi-build-dependencies.yaml +++ /dev/null @@ -1,52 +0,0 @@ -name: "Multiple builds with dependencies" -description: "Validates that multiple builds trigger correctly based on their source paths" - -setup: - config: - trunk_branch: main - environments: - - dev - - prod - builds: - - name: frontend - triggers: - - "frontend/**" - - name: backend - triggers: - - "backend/**" - - name: shared - triggers: - - "shared/**" - - commits: - - message: "feat: add frontend application" - files: - frontend/index.html: | - - - Hello Frontend - - - message: "feat: add backend service" - files: - backend/server.go: | - package main - - func main() { - println("Backend server") - } - - message: "feat: add shared library" - files: - shared/utils.go: | - package shared - - func Helper() string { - return "shared" - } - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: success diff --git a/e2e/scenarios/orchestrate/no-env-finalize-writes-correct-state.yaml b/e2e/scenarios/orchestrate/no-env-finalize-writes-correct-state.yaml deleted file mode 100644 index 5ae4c28..0000000 --- a/e2e/scenarios/orchestrate/no-env-finalize-writes-correct-state.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: "No-environment finalize writes correct state structure" -description: "Validates that finalize job writes state under prerelease key (state.prerelease.sha) not root (state.sha)" - -setup: - config: - trunk_branch: main - environments: [] # No environments - library/CLI project - builds: - - name: cli - triggers: - - "cmd/**" - - commits: - - message: "feat: CLI feature" - files: - cmd/main.go: | - package main - - func main() { - println("v2 CLI") - } - - # Start with existing state to ensure finalize preserves structure - manifest: - ci: - state: - prerelease: - sha: "0000000000000000000000000000000000000000" - version: "v0.1.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - - tags: - - "v0.1.0-rc.0" - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: success - # Verify state is written under prerelease key, not at root - manifest: - ci: - state: - prerelease: - sha: HEAD - # Version should increment (0.1.0 -> 0.2.0 for feat commit) diff --git a/e2e/scenarios/orchestrate/no-environment-build.yaml b/e2e/scenarios/orchestrate/no-environment-build.yaml deleted file mode 100644 index afb6a22..0000000 --- a/e2e/scenarios/orchestrate/no-environment-build.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: "No-environment build triggered" -description: "Validates that orchestrate workflow works correctly with zero environments (library/CLI projects)" - -setup: - config: - trunk_branch: main - environments: [] # No environments - library/CLI project - builds: - - name: cli - triggers: - - "cmd/**" - - "internal/**" - - commits: - - message: "feat: initial CLI implementation" - files: - cmd/main.go: | - package main - - func main() { - println("Hello from CLI") - } - internal/lib/lib.go: | - package lib - - func Greet() string { - return "Hello" - } - -trigger: - workflow: orchestrate.yaml - event: push - -expect: - workflow: - conclusion: success - manifest: - ci: - state: - prerelease: - sha: HEAD diff --git a/e2e/scenarios/promote/dev-to-test-success.yaml b/e2e/scenarios/promote/dev-to-test-success.yaml deleted file mode 100644 index 397ad81..0000000 --- a/e2e/scenarios/promote/dev-to-test-success.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: "Promote from dev to test" -description: "Validates successful promotion from dev environment to test environment" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - workflow: "" - triggers: - - "src/**" - deploys: - - name: services - workflow: "" - triggers: [] - depends_on: - - app - - commits: - - message: "feat: initial application" - files: - src/main.go: | - package main - - func main() { - println("Hello World") - } - - manifest: - ci: - state: - dev: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - test: {} - prod: {} - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "dev-to-test" - -expect: - workflow: - conclusion: success - manifest: - ci: - state: - dev: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - test: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" diff --git a/e2e/scenarios/promote/no-env-promote-with-state.yaml b/e2e/scenarios/promote/no-env-promote-with-state.yaml deleted file mode 100644 index aba0776..0000000 --- a/e2e/scenarios/promote/no-env-promote-with-state.yaml +++ /dev/null @@ -1,64 +0,0 @@ -name: "No-environment promotion with existing state" -description: "Validates promotion correctly parses nested prerelease state for no-environment repos" - -setup: - config: - trunk_branch: main - environments: [] # No environments - library/CLI project - builds: - - name: cli - triggers: - - "cmd/**" - - "internal/**" - - commits: - - message: "feat: initial CLI implementation" - files: - cmd/main.go: | - package main - - func main() { - println("Hello from CLI") - } - internal/lib/lib.go: | - package lib - - func Greet() string { - return "Hello" - } - - # Pre-existing state in the correct nested structure - # This tests that promote can parse state.prerelease.sha correctly - manifest: - ci: - state: - prerelease: - sha: "{{HEAD}}" - version: "v1.0.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - - releases: - - tag: "v1.0.0-rc.0" - prerelease: true - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "default" - -expect: - workflow: - conclusion: success - manifest: - ci: - state: - prerelease: - wiped: true - release: - sha: "{{HEAD}}" - version: "v1.0.0" - releases: - - tag: "v1.0.0" - prerelease: false diff --git a/e2e/scenarios/promote/promote-fails-missing-source.yaml b/e2e/scenarios/promote/promote-fails-missing-source.yaml index 762c99e..e2c01a4 100644 --- a/e2e/scenarios/promote/promote-fails-missing-source.yaml +++ b/e2e/scenarios/promote/promote-fails-missing-source.yaml @@ -1,51 +1,36 @@ -name: "Promotion fails with missing source" -description: "Validates that promotion fails when source environment has no deployment" +name: "Promote fails on missing source env" +description: | + Drives a real promote preflight failure under act: a commit lands on trunk + but is never orchestrated, so dev has no deployment. Promoting dev-to-test + then fails preflight because the source env is empty. expect_failure on the + promote step makes that failure the success path, proving the harness + observes a genuinely failing promote rather than a simulated outcome. -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - workflow: "" - triggers: - - "src/**" - deploys: - - name: services - workflow: "" - triggers: [] - depends_on: - - app +config: + trunk_branch: main + environments: [dev, test, prod] + builds: + - name: app + workflow: build.yaml + triggers: ["src/**"] + deploys: + - name: cdk + workflow: deploy.yaml + triggers: ["cdk/**"] - commits: - - message: "feat: initial application" +steps: + - name: "Commit source but never orchestrate, leaving dev empty" + action: commit + commit: + message: "feat: add app source" files: - src/main.go: | + src/app.go: | package main + func main() {} - func main() { - println("Hello World") - } - - manifest: - ci: - state: - dev: {} - test: {} - prod: {} - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "dev-to-test" - -expect: - workflow: - conclusion: failure - jobs: - preflight: - conclusion: failure + - name: "Promote dev to test fails because dev has no deployment" + action: promote + promote: + mode: cascade + target: test + expect_failure: true diff --git a/e2e/scenarios/promote/test-to-prod-final.yaml b/e2e/scenarios/promote/test-to-prod-final.yaml deleted file mode 100644 index a9b2da4..0000000 --- a/e2e/scenarios/promote/test-to-prod-final.yaml +++ /dev/null @@ -1,84 +0,0 @@ -name: "Final production release" -description: "Validates promotion to production creates a final published release" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - uat - - prod - builds: - - name: app - workflow: "" - triggers: - - "src/**" - deploys: - - name: services - workflow: "" - triggers: [] - depends_on: - - app - - commits: - - message: "feat: initial application" - files: - src/main.go: | - package main - - func main() { - println("Hello World") - } - - manifest: - ci: - state: - dev: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - test: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - uat: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - prod: {} - - releases: - - tag: "v0.1.0-rc.0" - prerelease: true - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "uat-to-prod" - -expect: - workflow: - conclusion: success - manifest: - ci: - state: - dev: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - test: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - uat: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - prod: - sha: "{{HEAD}}" - version: "0.1.0" - releases: - - tag: "v0.1.0" - prerelease: false diff --git a/e2e/scenarios/release/changelog-generated.yaml b/e2e/scenarios/release/changelog-generated.yaml deleted file mode 100644 index 629674e..0000000 --- a/e2e/scenarios/release/changelog-generated.yaml +++ /dev/null @@ -1,83 +0,0 @@ -name: "Changelog generated from commits" -description: "Validates that a changelog is automatically generated from conventional commits during release" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - triggers: - - "src/**" - - commits: - - message: "feat: add payment processing module" - files: - src/payment.go: | - package main - - func ProcessPayment(amount float64) bool { - return amount > 0 - } - - message: "feat: implement refund functionality" - files: - src/refund.go: | - package main - - func ProcessRefund(transactionId string) error { - return nil - } - - message: "fix: correct payment validation logic" - files: - src/payment.go: | - package main - - func ProcessPayment(amount float64) bool { - if amount <= 0 { - return false - } - return true - } - - message: "docs: update payment API documentation" - files: - docs/api.md: | - # Payment API - - Process payments and refunds. - - tags: - - "v0.2.0-rc.0" - - manifest: - ci: - state: - dev: - sha: "{{HEAD}}" - version: "0.2.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "dev-to-prod" - -expect: - workflow: - conclusion: success - tags: - - pattern: "v0.2.0" - release: - tag: "v0.2.0" - changelog: - contains: - - "feat: add payment processing module" - - "feat: implement refund functionality" - - "fix: correct payment validation logic" - sections: - - "Features" - - "Bug Fixes" diff --git a/e2e/scenarios/release/final-release-published.yaml b/e2e/scenarios/release/final-release-published.yaml deleted file mode 100644 index 2122c1e..0000000 --- a/e2e/scenarios/release/final-release-published.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: "Final release published" -description: "Validates that a final semver tag and GitHub release are created when promoting to production" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - triggers: - - "src/**" - - commits: - - message: "feat: implement user authentication" - files: - src/auth.go: | - package main - - func Authenticate(user string) bool { - return user != "" - } - - message: "feat: add user profile management" - files: - src/profile.go: | - package main - - type Profile struct { - Username string - Email string - } - - func GetProfile(user string) *Profile { - return &Profile{Username: user} - } - - message: "fix: handle empty username edge case" - files: - src/auth.go: | - package main - - func Authenticate(user string) bool { - if user == "" { - return false - } - return true - } - - tags: - - "v0.1.0-rc.0" - - manifest: - ci: - state: - dev: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "dev-to-prod" - -expect: - workflow: - conclusion: success - tags: - - pattern: "v0.1.0" - release: - tag: "v0.1.0" - draft: false - prerelease: false diff --git a/e2e/scenarios/release/rc-tag-created.yaml b/e2e/scenarios/release/rc-tag-created.yaml deleted file mode 100644 index c45577f..0000000 --- a/e2e/scenarios/release/rc-tag-created.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: "RC tag created on prerelease" -description: "Validates that an RC (release candidate) tag is created when promoting to a prerelease environment" - -setup: - config: - trunk_branch: main - environments: - - dev - - test - - prod - builds: - - name: app - triggers: - - "src/**" - - commits: - - message: "feat: add new feature for release" - files: - src/feature.go: | - package main - - func Feature() string { - return "new feature" - } - - message: "fix: minor bug fix" - files: - src/bugfix.go: | - package main - - func BugFix() bool { - return true - } - - manifest: - ci: - state: - dev: - sha: "{{HEAD}}" - version: "0.1.0-rc.0" - committed_at: "2024-01-01T00:00:00Z" - committed_by: "system" - -trigger: - workflow: promote.yaml - event: workflow_dispatch - inputs: - mode: "dev-to-test" - -expect: - workflow: - conclusion: success - tags: - - pattern: "v0.1.0-rc.0"