Skip to content

Commit 2e63c52

Browse files
intel352claude
andcommitted
fix: modernize hyphen-steps rule precision — only rename step refs, not arbitrary substrings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent be6530a commit 2e63c52

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

modernize/rules_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package modernize
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"gopkg.in/yaml.v3"
8+
)
9+
10+
func parseYAML(t *testing.T, input string) *yaml.Node {
11+
t.Helper()
12+
var root yaml.Node
13+
if err := yaml.Unmarshal([]byte(input), &root); err != nil {
14+
t.Fatalf("failed to parse YAML: %v", err)
15+
}
16+
return &root
17+
}
18+
19+
func TestHyphenSteps_DoesNotCorruptUnrelatedValues(t *testing.T) {
20+
// The step name is "check-xss" but a config value contains "check-xss" as a
21+
// substring in a URL or description. The fix must NOT alter those unrelated values.
22+
input := `
23+
pipelines:
24+
security:
25+
steps:
26+
- name: check-xss
27+
type: step.set
28+
config:
29+
values:
30+
status: "running check-xss-scanner v2"
31+
url: "https://example.com/check-xss-endpoint"
32+
next: log-result
33+
- name: log-result
34+
type: step.set
35+
config:
36+
values:
37+
message: "done"
38+
`
39+
root := parseYAML(t, input)
40+
rule := hyphenStepsRule()
41+
changes := rule.Fix(root)
42+
43+
out, err := yaml.Marshal(root)
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
output := string(out)
48+
49+
// Step name should be renamed
50+
if !strings.Contains(output, "name: check_xss") {
51+
t.Error("expected step name to be renamed to check_xss")
52+
}
53+
54+
// next reference should be renamed
55+
if !strings.Contains(output, "next: log_result") {
56+
t.Error("expected next reference to be renamed to log_result")
57+
}
58+
59+
// Unrelated values must NOT be corrupted
60+
if !strings.Contains(output, "running check-xss-scanner v2") {
61+
t.Errorf("unrelated config value was corrupted: status field should still contain 'running check-xss-scanner v2'\noutput:\n%s", output)
62+
}
63+
if !strings.Contains(output, "https://example.com/check-xss-endpoint") {
64+
t.Errorf("unrelated config value was corrupted: url field should still contain original URL\noutput:\n%s", output)
65+
}
66+
67+
if len(changes) == 0 {
68+
t.Error("expected at least one change")
69+
}
70+
t.Logf("changes: %d", len(changes))
71+
for _, c := range changes {
72+
t.Logf(" %s (line %d): %s", c.RuleID, c.Line, c.Description)
73+
}
74+
}
75+
76+
func TestHyphenSteps_RenamesTemplateIndexExpressions(t *testing.T) {
77+
input := `
78+
pipelines:
79+
main:
80+
steps:
81+
- name: parse-request
82+
type: step.request_parse
83+
config: {}
84+
- name: use-data
85+
type: step.set
86+
config:
87+
values:
88+
result: '{{ index .steps "parse-request" "body" }}'
89+
`
90+
root := parseYAML(t, input)
91+
rule := hyphenStepsRule()
92+
rule.Fix(root)
93+
94+
out, err := yaml.Marshal(root)
95+
if err != nil {
96+
t.Fatal(err)
97+
}
98+
output := string(out)
99+
100+
if !strings.Contains(output, `index .steps "parse_request" "body"`) {
101+
t.Errorf("template index expression not updated\noutput:\n%s", output)
102+
}
103+
if !strings.Contains(output, "name: parse_request") {
104+
t.Error("step name not renamed")
105+
}
106+
}
107+
108+
func TestHyphenSteps_RenamesConditionalFieldPaths(t *testing.T) {
109+
input := `
110+
pipelines:
111+
main:
112+
steps:
113+
- name: check-status
114+
type: step.set
115+
config:
116+
values:
117+
matched: "true"
118+
- name: branch
119+
type: step.conditional
120+
config:
121+
field: steps.check-status.matched
122+
routes:
123+
"true": handle-true
124+
default: handle-false
125+
- name: handle-true
126+
type: step.set
127+
config:
128+
values:
129+
msg: ok
130+
- name: handle-false
131+
type: step.set
132+
config:
133+
values:
134+
msg: fail
135+
`
136+
root := parseYAML(t, input)
137+
rule := hyphenStepsRule()
138+
rule.Fix(root)
139+
140+
out, err := yaml.Marshal(root)
141+
if err != nil {
142+
t.Fatal(err)
143+
}
144+
output := string(out)
145+
146+
if !strings.Contains(output, "steps.check_status.matched") {
147+
t.Errorf("conditional field path not updated\noutput:\n%s", output)
148+
}
149+
if !strings.Contains(output, "name: check_status") {
150+
t.Error("step name not renamed")
151+
}
152+
// Route values and default that are exact step name matches
153+
if !strings.Contains(output, "handle_true") {
154+
t.Errorf("route value not updated\noutput:\n%s", output)
155+
}
156+
if !strings.Contains(output, "handle_false") {
157+
t.Errorf("default value not updated\noutput:\n%s", output)
158+
}
159+
}
160+
161+
func TestHyphenSteps_Check(t *testing.T) {
162+
input := `
163+
pipelines:
164+
main:
165+
steps:
166+
- name: my-step
167+
type: step.set
168+
config: {}
169+
- name: safe_step
170+
type: step.set
171+
config: {}
172+
`
173+
root := parseYAML(t, input)
174+
rule := hyphenStepsRule()
175+
findings := rule.Check(root, []byte(input))
176+
177+
if len(findings) != 1 {
178+
t.Fatalf("expected 1 finding, got %d", len(findings))
179+
}
180+
if findings[0].Message == "" {
181+
t.Error("finding should have a message")
182+
}
183+
if !findings[0].Fixable {
184+
t.Error("finding should be fixable")
185+
}
186+
}

0 commit comments

Comments
 (0)