Skip to content

Commit 0fc44f4

Browse files
Copilotnitrocodeautofix-ci[bot]aknyshclaude
authored
refactor(terraform): reduce ExecuteTerraform complexity 160→9, improve test coverage (#2226)
* Initial plan * refactor: extract helpers from ExecuteTerraform (complexity 160→26) with unit tests Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * docs: add blog post, roadmap entry, CLAUDE.md update for ExecuteTerraform refactor Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * [autofix.ci] apply automated fixes * fix: address CodeRabbit issues and improve test coverage for ExecuteTerraform helpers Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * fix: address all CodeRabbit issues, reduce complexity to ≤12, split helpers file to 3 files <600 lines each - Remove dead `varFile string` param from printAndWriteVarFiles (CodeRabbit 🔴 #1) - Remove setupTerraformAuthCreator, reuse defaultAuthManagerCreator (CodeRabbit 🔴 #2) - Split terraform_execute_helpers.go (786 lines) into 3 files all <600 lines (CodeRabbit 🔴 #3): - terraform_execute_helpers.go: auth/env/init/validation helpers (488 lines) - terraform_execute_helpers_args.go: arg builders (156 lines) - terraform_execute_helpers_exec.go: execution pipeline + workspace/TTY/cleanup (320 lines) - Add mutual exclusion comments to buildInitSubcommandArgs + executeTerraformInitPhase (CodeRabbit 🟡 #4) - Document buildInitArgs vs buildInitSubcommandArgs workspace/reconfigure asymmetry (CodeRabbit 🟡 #5) - Restore 'Resolving auth config for terraform command' debug entry log (CodeRabbit 🟡 #6) - Restore 'Stored authenticated identity for hooks' suffix in utils_auth.go (CodeRabbit 🟡 #7) - Extract prepareComponentExecution + executeCommandPipeline from ExecuteTerraform, reducing cyclomatic complexity from 25 → 9 (well below the 15 target) - Add tests: resolveExitCode (nil/ExitCodeError/generic/wrapped), buildWorkspaceSubcommandArgs (delete/select/no-subcommand2), buildTerraformCommandArgs (validate subcommand), prepareComponentExecution (no path error), executeCommandPipeline (TTY error via nil stdin) Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * [autofix.ci] apply automated fixes * Fix setupTerraformAuth error wrapping and document pre-hook termination behavior Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * Add setupTerraformAuth unit tests to fix unused import compile error Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * fix(tests): address CodeRabbit Pass 4 feedback — strengthen assertions, add ErrInvalidAuthConfig test, clean up empty section - Add require.Error(t, err) to TestPrepareComponentExecution_NoComponentPath_ReturnsError (was coverage theater: _ = err with no assertion) - Add defaultMergedAuthConfigGetter injectable var to setupTerraformAuth so the ErrInvalidAuthConfig wrap branch is testable without requiring a real MergeComponentAuthFromConfig failure - Add TestSetupTerraformAuth_MergedConfigError_WrapsWithInvalidAuthConfig covering the previously-untested ErrInvalidAuthConfig wrap path (non-ErrInvalidComponent errors) - Remove empty addRegionEnvVarForImport section placeholder from coverage test file (those 3 tests already exist in terraform_execute_helpers_test.go) - Update coverage test file header to reflect the correct list of covered functions Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * fix(tests): address CodeRabbit Pass 5 — split oversized test files, fix misleading wsOpts comment, add wsOpts branch test, document defaultMergedAuthConfigGetter injection layers Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * [autofix.ci] apply automated fixes * fix(tests): remove unused 'os' import from terraform_execute_helpers_test.go after file split Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> * fix: audit ExecuteTerraform refactor — lint fixes, test cleanup, fix doc Lint fixes (16 issues): - Fix hugeParam: handleVersionSubcommand now takes pointers - Fix unlambda: defaultMergedAuthConfigGetter uses direct function ref - Fix filepathJoin: split "infra/networking" into separate segments - Fix unparam: remove unused planFile from buildApplySubcommandArgs - Fix forbidigo: add nolint for TF_WORKSPACE (Terraform convention) - Fix nestif: extract handlePlanStatusUpload from executeMainTerraformCommand - Fix argument-limit: add nolint for variadic opts parameter - Fix cyclomatic: extract shouldSkipWorkspaceSetup from runWorkspaceSetup - Fix cyclomatic: extract runPreExecutionSteps from prepareComponentExecution - Fix cyclomatic: extract autoGenerateComponentFiles, provisionComponentSource - Fix cyclomatic/funlen: extract logAndWriteComponentVars, logCliVarsOverrides - Fix add-constant: add subcommandApply/Deploy/Init/Workspace, dirPermissions Test cleanup: - Remove 4 duplicate resolveExitCode tests from _pipeline_test.go - Clarify tautological cleanup test comment - Remove unused writeTestFile helper Add fix doc documenting all audit findings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit audit pass 7 — remove duplicates, add coverage - Remove duplicate TestBuildWorkspaceSubcommandArgs_NoSubCommand2 from _pipeline_test.go - Remove tautological cleanup tests from _args_test.go (real tests in _coverage_test.go) - Remove redundant TestWarnOnConflictingEnvVars_NoConflictsNoError - Add TestAssembleComponentEnvVars_NonNilTenv (tenv != nil branch coverage) - Add TestBuildTerraformCommandArgs_Init (init switch-case dispatch) - Add comment to generateConfigFiles re: double GenerateFilesForComponent invocation - Document logTerraformContext tests as logging-only (not coverage theater) - Update fix doc with all 10 audit pass 7 items and resolutions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address audit pass 8 — debug-level branch coverage, final cleanup - Add TestPrintAndWriteVarFiles_DebugLogLevel_Success (item 8) - Add TestPrintAndWriteVarFiles_DebugLogLevel_CliVarsSection (item 8) - Add TestPrintAndWriteVarFiles_TraceLogLevel (item 8) - Validate item 5: storeAutoDetectedIdentity already tested in utils_auth_test.go (gomock strict controller implicitly verifies GetChain not called) - Validate item 10: generateConfigFiles comment already added in previous commit - Update fix doc with all audit pass 8 resolutions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address audit pass 9 — explicit identity guard test, call-site comment - Add TestStoreAutoDetectedIdentity_ExistingIdentity_NotOverwritten with explicit GetChain().Times(0) assertion (item 5) - Add double-invocation comment at generateConfigFiles call site in runPreExecutionSteps (item 10) - Update fix doc with pass 9 resolutions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review — error masking, empty workdir, cross-platform test - Separate provisioning errors from "component does not exist" in resolveAndProvisionComponentPath — propagate original error directly - Guard empty _workdir_path in provisionComponentSource to match prepareInitExecution behavior - Replace Unix-only "false" command with cross-platform "go run" in TestResolveExitCode_OsExecExitError - Remove warnOnConflictingEnvVars coverage theater tests (logging-only function, NotPanics assertions add no value) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: aknysh <andriy.knysh@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bb6f71d commit 0fc44f4

15 files changed

+3368
-720
lines changed

CLAUDE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,14 @@ Always ask first: "This will discard uncommitted changes. Proceed? [y/N]"
362362
### Test Coverage (MANDATORY)
363363
80% minimum (CodeCov enforced). All features need tests. `make testacc-coverage` for reports.
364364

365+
### Cyclomatic Complexity (MANDATORY)
366+
golangci-lint enforces `cyclop: max-complexity: 15` and `funlen: lines: 60, statements: 40`.
367+
When refactoring high-complexity functions:
368+
1. Extract blocks with clear single responsibilities into named helper functions.
369+
2. Use the pattern: `buildXSubcommandArgs`, `resolveX`, `checkX`, `assembleX`, `handleX`.
370+
3. Keep the orchestrator function as a flat linear pipeline of named steps (see `ExecuteTerraform`).
371+
4. Previously high-complexity functions: `ExecuteTerraform` (160→26, see `internal/exec/terraform.go`), `ExecuteDescribeStacks` (247→10), `processArgsAndFlags`.
372+
365373
### Environment Variables (MANDATORY)
366374
Use `viper.BindEnv("ATMOS_VAR", "ATMOS_VAR", "FALLBACK")` - ATMOS_ prefix required.
367375

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# `ExecuteTerraform` Refactor Audit — Findings and Fixes
2+
3+
**Date:** 2026-03-20
4+
5+
**Related PR:** #2226`refactor(terraform): reduce ExecuteTerraform cyclomatic complexity 160→26 with 100+ unit tests`
6+
7+
**Severity:** Low–Medium — test quality improvements, no production bugs found
8+
9+
---
10+
11+
## What PR #2226 Did
12+
13+
PR #2226 refactored `ExecuteTerraform` — formerly a ~900-line monolith with cyclomatic
14+
complexity 160 — into a clean pipeline of 26 focused helper functions across 4 files:
15+
16+
- **`terraform.go`** (185 lines) — orchestrator: `ExecuteTerraform``prepareComponentExecution`
17+
`executeCommandPipeline``cleanupTerraformFiles`.
18+
- **`terraform_execute_helpers.go`** (512 lines) — auth, env vars, init, validation helpers.
19+
- **`terraform_execute_helpers_args.go`** (156 lines) — argument builders for plan/apply/init/workspace.
20+
- **`terraform_execute_helpers_exec.go`** (322 lines) — execution pipeline, workspace setup, TTY, cleanup.
21+
22+
All files are within the 600-line limit. 100+ unit tests were added across 6 test files.
23+
24+
### Key design decisions
25+
26+
- Injectable test vars (`defaultMergedAuthConfigGetter`, `defaultComponentConfigFetcher`,
27+
`defaultAuthManagerCreator`) enable isolated unit testing of auth paths without real
28+
infrastructure.
29+
- Mutual exclusion between `executeTerraformInitPhase` and `buildInitSubcommandArgs` is
30+
well-documented (they share `prepareInitExecution` but are guarded by
31+
`SubCommand == "init"` branching).
32+
33+
---
34+
35+
## Audit Findings
36+
37+
### Finding 1: Duplicate `resolveExitCode` tests across files
38+
39+
**Severity:** Low (test duplication)
40+
41+
`resolveExitCode` was tested in both `terraform_execute_helpers_test.go` (5 test functions
42+
including `os/exec.ExitError`) and `terraform_execute_helpers_pipeline_test.go` (4 test
43+
functions, subset). The `_test.go` version is a superset.
44+
45+
**Fix:** Removed the 4 duplicate tests from `_pipeline_test.go`. The 5 canonical tests in
46+
`_test.go` remain.
47+
48+
### Finding 2: Tautological cleanup test
49+
50+
**Severity:** Low (misleading test)
51+
52+
`TestCleanupTerraformFiles_ApplyRemovesVarFile` in `_args_test.go` created a file but only
53+
asserted `NotPanics` — never verifying the file was removed. A proper file-removal test already
54+
exists in `_coverage_test.go` (`TestCleanupTerraformFiles_ApplyRemovesVarfileForReal`).
55+
56+
**Fix:** Clarified the test's purpose in its comment — it tests graceful handling of
57+
mismatched path layouts, not actual removal. The real removal test is in `_coverage_test.go`.
58+
59+
### Finding 3: No tests for `resolveAndProvisionComponentPath`
60+
61+
**Severity:** High (coverage gap)
62+
63+
This ~55-line function handles auto-generate files, JIT source provisioning, workdir path
64+
override, and re-checking directory existence. It has zero dedicated unit tests. Testing
65+
requires mocking `generateConfigFiles`, `PrepareComponentSourceForJITProvisioning`, and
66+
filesystem operations.
67+
68+
**Status:** Documented. Requires significant mocking infrastructure to test properly —
69+
deferred to a follow-up PR.
70+
71+
### Finding 4: No tests for `generateConfigFiles`, `executeTerraformInitPhase`, `handleVersionSubcommand`
72+
73+
**Severity:** Medium (coverage gaps)
74+
75+
These orchestration functions combine sub-calls but have no tests verifying error propagation
76+
through the combined flow.
77+
78+
**Status:** Documented. These are orchestration functions that delegate to individually-tested
79+
helpers; the risk is lower than Finding 3.
80+
81+
### Finding 5: `TestRunWorkspaceSetup_OutputSubcommand_DryRun_RedirectsToStderr` is misleading
82+
83+
**Severity:** Low (misleading test name)
84+
85+
The test claims to exercise the `wsOpts` stderr-redirect branch for "output"/"show"
86+
subcommands, but because `DryRun=true`, `ExecuteShellCommand` returns nil without ever using
87+
the options. The test only verifies the function does not error.
88+
89+
**Status:** Documented. The test name is misleading about what it covers, but the behavior
90+
is correct.
91+
92+
### Finding 6: Hardcoded `/dev/stdout` in `runWorkspaceSetup`
93+
94+
**Severity:** Low (pre-existing, cross-platform)
95+
96+
`terraform_execute_helpers_exec.go` line 167 uses `"/dev/stdout"` which doesn't exist on
97+
Windows. This is a pre-existing pattern (not introduced by this PR), and `ExecuteShellCommand`
98+
already handles Windows conversion for `/dev/null`.
99+
100+
**Status:** Pre-existing. Not a regression.
101+
102+
---
103+
104+
## Audit Pass 7 — CodeRabbit Review Items
105+
106+
### Item 1: Duplicate resolveExitCode tests (Pass 6 #1)
107+
108+
**Status:** Fixed. Removed 4 duplicate tests from `_pipeline_test.go`.
109+
110+
### Item 2: Duplicate TestBuildWorkspaceSubcommandArgs_NoSubCommand2 (Pass 6 #2)
111+
112+
**Status:** Fixed. Removed from `_pipeline_test.go`; canonical test is
113+
`TestBuildWorkspaceSubcommandArgs_Bare` in `_args_test.go`.
114+
115+
### Item 3: Tautological cleanup tests (Pass 6 #3)
116+
117+
**Status:** Fixed. Removed `TestCleanupTerraformFiles_ApplyRemovesVarFile` and
118+
`TestCleanupTerraformFiles_PlanSubcommandSkipsCleanup` from `_args_test.go`. Real
119+
file-assertion tests exist in `_coverage_test.go`.
120+
121+
### Item 4: logTerraformContext coverage theater
122+
123+
**Status:** Kept with documentation. The function is logging-only — it calls `log.Debug`
124+
which is not capturable in unit tests without injecting a logger. The NotPanics tests
125+
are the best we can do without over-engineering. Comment added explaining this.
126+
127+
### Item 5: storeAutoDetectedIdentity no-op branch untested
128+
129+
**Status:** Fixed. Added `TestStoreAutoDetectedIdentity_ExistingIdentity_NotOverwritten`
130+
to `_auth_test.go` with explicit `GetChain().Times(0)` assertion. The existing table-driven
131+
test in `utils_auth_test.go` also covers this via gomock strict mode, but the explicit
132+
`Times(0)` makes the contract self-documenting.
133+
134+
### Item 6: Redundant warnOnConflictingEnvVars test
135+
136+
**Status:** Fixed. Removed `TestWarnOnConflictingEnvVars_NoConflictsNoError` from
137+
`_args_test.go`. The 5 `t.Setenv` tests in `_coverage_test.go` implicitly cover the
138+
clean-environment path.
139+
140+
### Item 7: assembleComponentEnvVars with tenv != nil untested
141+
142+
**Status:** Fixed. Added `TestAssembleComponentEnvVars_NonNilTenv` that passes a
143+
zero-value `*dependencies.ToolchainEnvironment`. The `tenv != nil` branch runs. Full
144+
PATH ordering test requires a real toolchain install (deferred).
145+
146+
### Item 8: printAndWriteVarFiles debug-print branches
147+
148+
**Status:** Fixed. Added 3 tests exercising LogLevelDebug and LogLevelTrace branches:
149+
- `TestPrintAndWriteVarFiles_DebugLogLevel_Success` — ComponentVarsSection at debug level.
150+
- `TestPrintAndWriteVarFiles_DebugLogLevel_CliVarsSection` — CLI vars override at debug level.
151+
- `TestPrintAndWriteVarFiles_TraceLogLevel` — trace level path.
152+
153+
### Item 9: buildTerraformCommandArgs init branch untested
154+
155+
**Status:** Fixed. Added `TestBuildTerraformCommandArgs_Init` that tests the `case "init"`
156+
dispatch including `-reconfigure` and `-var-file` flags.
157+
158+
### Item 10: GenerateFilesForComponent double-invocation
159+
160+
**Status:** Documented. Added comment to `generateConfigFiles` explaining that
161+
`autoGenerateComponentFiles` and `generateConfigFiles` serve different purposes (generate
162+
sections vs backend/provider overrides). Pre-existing behavior, not a regression.
163+
164+
---
165+
166+
## Changes Made
167+
168+
### `internal/exec/terraform.go`
169+
170+
- Add `subcommandApply`, `subcommandDeploy`, `subcommandInit`, `subcommandWorkspace`,
171+
`dirPermissions` constants.
172+
173+
### `internal/exec/terraform_execute_helpers.go`
174+
175+
- Fix `hugeParam`: `handleVersionSubcommand` takes pointer params.
176+
- Fix `unlambda`: `defaultMergedAuthConfigGetter` uses direct function ref.
177+
- Fix `add-constant`: replace string literals with named constants.
178+
- Fix `cyclomatic`/`funlen`: extract `autoGenerateComponentFiles`,
179+
`provisionComponentSource`, `logAndWriteComponentVars`, `logCliVarsOverrides`.
180+
- Add comment to `generateConfigFiles` re: double-invocation (item 10).
181+
182+
### `internal/exec/terraform_execute_helpers_args.go`
183+
184+
- Fix `unparam`: remove unused `planFile` from `buildApplySubcommandArgs`.
185+
- Replace `"apply"`, `"init"`, `"workspace"` with constants.
186+
187+
### `internal/exec/terraform_execute_helpers_exec.go`
188+
189+
- Fix `forbidigo`: nolint for `TF_WORKSPACE`.
190+
- Fix `nestif`: extract `handlePlanStatusUpload`.
191+
- Fix `argument-limit`: nolint for variadic opts.
192+
- Fix `cyclomatic`: extract `shouldSkipWorkspaceSetup`, `runPreExecutionSteps`.
193+
194+
### `internal/exec/terraform_execute_helpers_pipeline_test.go`
195+
196+
- Remove duplicate `resolveExitCode` tests and `TestBuildWorkspaceSubcommandArgs_NoSubCommand2`.
197+
- Clean up unused imports.
198+
199+
### `internal/exec/terraform_execute_helpers_args_test.go`
200+
201+
- Remove tautological cleanup tests and redundant `warnOnConflictingEnvVars` test.
202+
- Add `TestAssembleComponentEnvVars_NonNilTenv` (item 7).
203+
- Add `TestBuildTerraformCommandArgs_Init` (item 9).
204+
- Add docs for logTerraformContext tests explaining logging-only functions (item 4).
205+
206+
### `internal/exec/terraform_execute_helpers_coverage_test.go`
207+
208+
- Add `TestPrintAndWriteVarFiles_DebugLogLevel_Success` (item 8).
209+
- Add `TestPrintAndWriteVarFiles_DebugLogLevel_CliVarsSection` (item 8).
210+
- Add `TestPrintAndWriteVarFiles_TraceLogLevel` (item 8).
211+
212+
### `internal/exec/terraform_execute_helpers_test.go`
213+
214+
- Fix `filepathJoin`: split `"infra/networking"` into separate path segments.
215+
216+
---
217+
218+
## References
219+
220+
- PR #2226: `refactor(terraform): reduce ExecuteTerraform cyclomatic complexity 160→26`
221+
- `internal/exec/terraform.go` — refactored orchestrator
222+
- `internal/exec/terraform_execute_helpers.go` — auth/env/init/validation helpers
223+
- `internal/exec/terraform_execute_helpers_args.go` — argument builders
224+
- `internal/exec/terraform_execute_helpers_exec.go` — execution pipeline
225+
- `internal/exec/terraform_execute_helpers_*_test.go` — 6 test files

0 commit comments

Comments
 (0)