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
2 changes: 1 addition & 1 deletion .github/workflows/pr-code-quality-reviewer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/pr-code-quality-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ Use `COMMENT` when all findings are non-blocking. Keep the overall review body c
## agent: `grumpy-coder`
---
description: Hyper-critical senior reviewer that aggressively finds merge-blocking issues in changed lines
model: small
model: claude-haiku-4.5
---
You are a grumpy senior engineer doing a hostile first-pass code review.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ADR-49800: Add Dedicated Unit Test File for Activation Step Helpers

**Date**: 2026-08-02
**Status**: Accepted
**Deciders**: Copilot

---

### Context

`pkg/workflow/compiler_activation_steps.go` contains a dense set of activation-job helper methods — including reaction steps, OAuth token checks, lock-file checks, version checks, skill installs, text-output setup, and status comment wiring — many of which return errors. Despite this complexity, none of these helpers had focused unit tests; their behavior was exercised only incidentally through broader compiler-level integration tests. This made it difficult to pinpoint regressions in individual helpers and left error-returning paths (such as sanitization domain computation failures) without direct coverage.

### Decision

We will add a dedicated unit test file (`pkg/workflow/compiler_activation_steps_test.go`) that tests each activation step helper in isolation, using lightweight `activationJobBuildContext` instances constructed directly rather than going through full workflow compilation. Each helper's success path and primary error/skip path will be covered by a focused table-driven or sub-test function.

### Alternatives Considered

#### Alternative 1: Rely solely on existing broad compiler tests

Keep the status quo and cover activation step behavior only through higher-level compiler tests that exercise the full compilation pipeline. This avoids adding a new test file but provides poor regression signal: a failure in any activation helper surfaces as a broad compiler test failure with no indication of which helper broke. Error-returning paths deep in individual helpers are impractical to trigger through the compilation surface.

#### Alternative 2: Add integration-style tests that compile full workflows

Write tests that call `Compiler.Compile(...)` end-to-end with fixture workflow data, then assert on the generated YAML. This would exercise the real pipeline but requires constructing valid full-workflow inputs for every helper scenario, making tests verbose and slow. It also makes it harder to isolate a specific helper's behavior when a test fails.

### Consequences

#### Positive
- Focused tests that directly exercise each helper make regressions in individual methods easy to identify without reading through a full compilation diff.
- Error-returning paths (e.g., malformed model causing sanitization failure) are now explicitly covered, reducing the risk of silent breakage.
- Tests run without a full compilation pass, keeping the unit-test suite fast.
- Writing focused tests surfaced two helpers (`addActivationSkillInstallSteps`, `addActivationSafeOutputMessagesEnv`) whose `error` return types were unreachable; both were removed, simplifying callers and eliminating misleading dead-code paths.

#### Negative
- Adds test code that must be maintained alongside `compiler_activation_steps.go`; renaming helpers or changing their signatures requires updating both files.
- The test helpers (`newActivationStepsTestCompiler`, `newActivationStepsTestContext`) partially duplicate the construction logic of production build contexts and may drift if the context struct evolves.

#### Neutral
- Tests are tagged `//go:build !integration` so they are excluded from integration test runs, consistent with the existing test build tag convention in this package.
- The new file lives in the same `workflow` package (not `workflow_test`), giving it access to unexported types such as `activationJobBuildContext`.

---

*ADR reviewed and accepted as part of PR #49800.*
4 changes: 1 addition & 3 deletions pkg/workflow/compiler_activation_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ func (c *Compiler) buildActivationJob(data *WorkflowData, preActivationJobCreate
if err := c.addActivationRepositoryAndOutputSteps(ctx); err != nil {
return nil, fmt.Errorf("failed to add activation repository and output steps: %w", err)
}
if err := c.addActivationSkillInstallSteps(ctx); err != nil {
return nil, fmt.Errorf("failed to add skill install steps: %w", err)
}
c.addActivationSkillInstallSteps(ctx)
if err := c.addActivationCommandAndLabelOutputs(ctx); err != nil {
return nil, fmt.Errorf("failed to add activation command and label outputs: %w", err)
}
Expand Down
22 changes: 8 additions & 14 deletions pkg/workflow/compiler_activation_steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func (c *Compiler) addActivationVersionCheckStep(ctx *activationJobBuildContext)
ctx.steps = append(ctx.steps, generateGitHubScriptWithRequire("check_version_updates.cjs"))
}

func (c *Compiler) addActivationSkillInstallSteps(ctx *activationJobBuildContext) error {
func (c *Compiler) addActivationSkillInstallSteps(ctx *activationJobBuildContext) {
skillRefs := append([]SkillReference(nil), ctx.data.SkillReferences...)
if len(skillRefs) == 0 && len(ctx.data.Skills) > 0 {
skillRefs = make([]SkillReference, 0, len(ctx.data.Skills))
Expand All @@ -182,7 +182,7 @@ func (c *Compiler) addActivationSkillInstallSteps(ctx *activationJobBuildContext
}
}
if len(skillRefs) == 0 {
return nil
return
}

engineID := resolveActivationEngineID(ctx.data)
Expand Down Expand Up @@ -243,8 +243,6 @@ func (c *Compiler) addActivationSkillInstallSteps(ctx *activationJobBuildContext

ctx.outputs["skill_install_failure_count"] = "${{ steps.collect-skill-install-failures.outputs.failure_count || '0' }}"
ctx.outputs["skill_install_errors"] = "${{ steps.collect-skill-install-failures.outputs.errors || '' }}"

return nil
}

func (c *Compiler) addActivationTextOutputStep(ctx *activationJobBuildContext) error {
Expand Down Expand Up @@ -312,9 +310,7 @@ func (c *Compiler) addActivationStatusCommentStep(ctx *activationJobBuildContext
if ctx.data.LockForAgent {
ctx.steps = append(ctx.steps, " GH_AW_LOCK_FOR_AGENT: \"true\"\n")
}
if err := addActivationSafeOutputMessagesEnv(ctx); err != nil {
return err
}
addActivationSafeOutputMessagesEnv(ctx)
ctx.steps = append(ctx.steps, " with:\n")
commentToken := c.resolveActivationToken(ctx.data)
if commentToken != "${{ secrets.GITHUB_TOKEN }}" {
Expand All @@ -328,18 +324,16 @@ func (c *Compiler) addActivationStatusCommentStep(ctx *activationJobBuildContext
return nil
}

func addActivationSafeOutputMessagesEnv(ctx *activationJobBuildContext) error {
func addActivationSafeOutputMessagesEnv(ctx *activationJobBuildContext) {
if ctx.data.SafeOutputs == nil || ctx.data.SafeOutputs.Messages == nil {
return nil
}
messagesJSON, err := serializeMessagesConfig(ctx.data.SafeOutputs.Messages)
if err != nil {
return fmt.Errorf("failed to serialize messages config for activation job: %w", err)
return
}
// serializeMessagesConfig uses json.Marshal on a struct containing only strings and bools,
// so it cannot fail in practice; the error is intentionally ignored here.
messagesJSON, _ := serializeMessagesConfig(ctx.data.SafeOutputs.Messages)
if messagesJSON != "" {
ctx.steps = append(ctx.steps, fmt.Sprintf(" GH_AW_SAFE_OUTPUT_MESSAGES: %q\n", messagesJSON))
}
return nil
}

func (c *Compiler) addActivationIssueLockStep(ctx *activationJobBuildContext) {
Expand Down
Loading
Loading