Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions e2e/harness/multistep.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,18 @@ type StepExpect struct {
}

// WorkflowFileExpect asserts a generated workflow file contains/excludes
// specific substrings. Verifies manifest fields make it into the emitted
// YAML, orthogonal to behavior checks (state/jobs/etc.) which observe the
// run outcome. Used for features whose effect is purely the generated
// workflow shape (#92 concurrency, #97 timeout-minutes, #101/#102 push
// retry loops).
// specific substrings, or asserts the file is absent entirely. Verifies
// manifest fields make it into the emitted YAML, orthogonal to behavior
// checks (state/jobs/etc.) which observe the run outcome. NotExists covers
// the conditional-generation case where a feature suppresses a whole file
// (for example the hotfix workflow when fewer than two environments exist).
// Used for features whose effect is purely the generated workflow shape
// (#92 concurrency, #97 timeout-minutes, #101/#102 push retry loops).
type WorkflowFileExpect struct {
Path string `yaml:"path"` // Path inside the test repo (e.g., ".github/workflows/orchestrate.yaml")
Contains []string `yaml:"contains,omitempty"` // Substrings that must appear
NotContains []string `yaml:"not_contains,omitempty"` // Substrings that must NOT appear
NotExists bool `yaml:"not_exists,omitempty"` // When true, the file must NOT exist
}

// StateExpect defines expected state for an environment
Expand Down
6 changes: 6 additions & 0 deletions e2e/harness/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,12 @@ func (r *Runner) assertWorkflowFile(ctx context.Context, expect WorkflowFileExpe
if reader != nil {
_, _ = io.Copy(&content, reader)
}
if expect.NotExists {
if exitCode == 0 {
return []error{fmt.Errorf("workflow file %s should not exist but was found", expect.Path)}
}
return nil
}
if exitCode != 0 {
return []error{fmt.Errorf("workflow file not found: %s", expect.Path)}
}
Expand Down
12 changes: 12 additions & 0 deletions e2e/harness/scenario.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ type Config struct {
// 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"`
// Validate, ValidateCheck, MergeQueue, PRPreview, Notify, and External carry
// the optional generator features through to the generated manifest untouched.
// Each uses a generic map (rather than a typed struct) so the harness stays
// decoupled from the generator's struct shapes while preserving every key
// across the marshal round-trip. As the generator gains new keys under any of
// these blocks, scenarios can exercise them without a harness change.
Validate map[string]any `yaml:"validate,omitempty"`
ValidateCheck map[string]any `yaml:"validate_check,omitempty"`
MergeQueue map[string]any `yaml:"merge_queue,omitempty"`
PRPreview map[string]any `yaml:"pr_preview,omitempty"`
Notify map[string]any `yaml:"notify,omitempty"`
External []map[string]any `yaml:"external,omitempty"`
}

// EnvEnvironmentConfig mirrors internal/config.EnvironmentConfig's gha_environment
Expand Down
38 changes: 38 additions & 0 deletions e2e/scenarios/14-validate-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: "Validate Check Workflow"
description: |
Verifies that enabling validate_check emits the standalone
cascade-validate.yaml workflow that runs the manifest parse on pull
requests touching the manifest file (#90).

Generator-output verification only.

config:
trunk_branch: main
environments: [dev]
builds:
- name: app
workflow: build.yaml
triggers: ["src/**"]
deploys: []
validate_check:
enabled: true

steps:
- name: "Initial commit; assert cascade-validate.yaml is generated"
action: commit
commit:
message: "feat: add app"
files:
src/app.go: |
package main
func main() {}
expect:
workflow_files:
- path: ".github/workflows/cascade-validate.yaml"
contains:
- "name: Validate Manifest"
- "pull_request:"
- "paths:"
- ".github/manifest.yaml"
- "validate-manifest:"
- "cascade parse-config"
37 changes: 37 additions & 0 deletions e2e/scenarios/15-merge-queue.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: "Merge Queue Workflow"
description: |
Verifies that enabling merge_queue emits the cascade-merge-queue.yaml
workflow that validates the manifest and sets up orchestration on
merge_group events (#91).

Generator-output verification only.

config:
trunk_branch: main
environments: [dev]
builds:
- name: app
workflow: build.yaml
triggers: ["src/**"]
deploys: []
merge_queue:
enabled: true

steps:
- name: "Initial commit; assert cascade-merge-queue.yaml is generated"
action: commit
commit:
message: "feat: add app"
files:
src/app.go: |
package main
func main() {}
expect:
workflow_files:
- path: ".github/workflows/cascade-merge-queue.yaml"
contains:
- "merge_group"
- "contents: read"
- "merge-queue-validate:"
- "cascade parse-config"
- "cascade --dry-run orchestrate setup"
37 changes: 37 additions & 0 deletions e2e/scenarios/16-pr-preview.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: "PR Preview Workflow"
description: |
Verifies that enabling pr_preview emits the cascade-pr-preview.yaml
workflow that plans a preview on pull requests, with a per-PR
concurrency group (#93).

Generator-output verification only.

config:
trunk_branch: main
environments: [dev]
builds:
- name: app
workflow: build.yaml
triggers: ["src/**"]
deploys: []
pr_preview:
enabled: true

steps:
- name: "Initial commit; assert cascade-pr-preview.yaml is generated"
action: commit
commit:
message: "feat: add app"
files:
src/app.go: |
package main
func main() {}
expect:
workflow_files:
- path: ".github/workflows/cascade-pr-preview.yaml"
contains:
- "name: Cascade PR Preview"
- "pull_request:"
- "cascade-pr-preview-${{ github.event.pull_request.number }}"
- "preview:"
- "Plan Preview"
37 changes: 37 additions & 0 deletions e2e/scenarios/17-validate-callback.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: "Validate Callback Gate"
description: |
Verifies that a top-level validate callback adds a validate job to the
generated orchestrate.yaml and gates the build jobs on its success via a
needs.validate.result == 'success' condition (#94).

Generator-output verification only.

config:
trunk_branch: main
environments: [dev]
builds:
- name: app
workflow: build.yaml
triggers: ["src/**"]
deploys:
- name: deploy-dev
workflow: deploy.yaml
triggers: ["src/**"]
validate:
run: "echo validate"

steps:
- name: "Initial commit; assert validate gate in orchestrate.yaml"
action: commit
commit:
message: "feat: add app"
files:
src/app.go: |
package main
func main() {}
expect:
workflow_files:
- path: ".github/workflows/orchestrate.yaml"
contains:
- "validate:"
- "needs.validate.result == 'success'"
51 changes: 51 additions & 0 deletions e2e/scenarios/hotfix/hotfix-generation-threshold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: "Hotfix Generation Threshold"
description: |
Verifies that a repository with two or more environments generates the
cascade-hotfix.yaml workflow with its full set of jobs and the finalize
step that records the merge, fix, and base SHAs (#88).

Generator-output verification only.

config:
trunk_branch: main
environments: [dev, prod]
builds:
- name: app
workflow: build.yaml
triggers: ["src/**"]
deploys:
- name: deploy-dev
workflow: deploy.yaml
triggers: ["src/**"]
- name: deploy-prod
workflow: deploy.yaml
triggers: ["src/**"]

steps:
- name: "Initial commit; assert cascade-hotfix.yaml is generated"
action: commit
commit:
message: "feat: add app"
files:
src/app.go: |
package main
func main() {}
expect:
workflow_files:
- path: ".github/workflows/cascade-hotfix.yaml"
contains:
- "workflow_dispatch:"
- "type: choice"
- "target_env:"
- "pull_request:"
- "types: [closed]"
- "'env/*'"
- "group: hotfix-"
- " plan:"
- " apply:"
- " check:"
- " context:"
- " finalize:"
- "--merge-sha"
- "--fix-sha"
- "--base-sha"
33 changes: 33 additions & 0 deletions e2e/scenarios/hotfix/hotfix-no-hotfix-single-env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "Hotfix Single Env Absent"
description: |
Verifies that a repository with a single environment does not generate
the cascade-hotfix.yaml workflow, since the hotfix flow only applies once
two or more environments exist (#88).

Generator-output verification only.

config:
trunk_branch: main
environments: [dev]
builds:
- name: app
workflow: build.yaml
triggers: ["src/**"]
deploys:
- name: deploy-dev
workflow: deploy.yaml
triggers: ["src/**"]

steps:
- name: "Initial commit; assert cascade-hotfix.yaml is absent"
action: commit
commit:
message: "feat: add app"
files:
src/app.go: |
package main
func main() {}
expect:
workflow_files:
- path: ".github/workflows/cascade-hotfix.yaml"
not_exists: true
Loading