Skip to content

Commit e28087c

Browse files
committed
Incorporating gemini suggestions
1 parent 31b249d commit e28087c

3 files changed

Lines changed: 139 additions & 22 deletions

File tree

pkg/validators/metadata_validators_test.go

Lines changed: 137 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ package validators
1616
import (
1717
"hpc-toolkit/pkg/config"
1818
"hpc-toolkit/pkg/modulereader"
19+
"strings"
1920
"testing"
2021

2122
"github.com/zclconf/go-cty/cty"
2223
)
2324

2425
func TestRegexValidator(t *testing.T) {
25-
// Fake blueprint for testing
26-
bp := config.Blueprint{
26+
// Base fake blueprint/module used by subtests
27+
baseBP := config.Blueprint{
2728
BlueprintName: "test-bp",
2829
Groups: []config.Group{
2930
{
@@ -41,24 +42,140 @@ func TestRegexValidator(t *testing.T) {
4142
},
4243
}
4344

44-
// Validation rule from metadata.yaml
45-
rule := modulereader.ValidationRule{
46-
Validator: "regex",
47-
ErrorMessage: "'name' must be lowercase and start with a letter.",
48-
Inputs: map[string]interface{}{
49-
"vars": []interface{}{"name"},
50-
"pattern": "^[a-z]([-a-z0-9]*[a-z0-9])?$",
51-
},
52-
}
53-
5445
validator := RegexValidator{}
55-
err := validator.Validate(bp, bp.Groups[0].Modules[0], rule, bp.Groups[0], 0)
56-
if err == nil {
57-
t.Errorf("Expected validation error, but got nil")
58-
}
5946

60-
expectedError := "deployment_groups[0].modules[0].settings.name: 'name' must be lowercase and start with a letter."
61-
if err.Error() != expectedError {
62-
t.Errorf("Expected error message '%s', but got '%s'", expectedError, err.Error())
63-
}
47+
t.Run("fails_on_invalid_name", func(t *testing.T) {
48+
// rule targeting module.setting "name"
49+
rule := modulereader.ValidationRule{
50+
Validator: "regex",
51+
ErrorMessage: "'name' must be lowercase and start with a letter.",
52+
Inputs: map[string]interface{}{
53+
"vars": []interface{}{"name"},
54+
"pattern": "^[a-z]([-a-z0-9]*[a-z0-9])?$",
55+
},
56+
}
57+
58+
err := validator.Validate(baseBP, baseBP.Groups[0].Modules[0], rule, baseBP.Groups[0], 0)
59+
if err == nil {
60+
t.Fatalf("expected validation error, got nil")
61+
}
62+
if !strings.Contains(err.Error(), "must be lowercase") {
63+
t.Fatalf("expected error message to contain 'must be lowercase', got: %q", err.Error())
64+
}
65+
})
66+
67+
t.Run("passes_on_valid_name", func(t *testing.T) {
68+
// valid module.setting value
69+
bp := baseBP
70+
bp.Groups[0].Modules[0].Settings = config.NewDict(map[string]cty.Value{
71+
"name": cty.StringVal("validname"),
72+
})
73+
74+
rule := modulereader.ValidationRule{
75+
Validator: "regex",
76+
ErrorMessage: "'name' must be lowercase and start with a letter.",
77+
Inputs: map[string]interface{}{
78+
"vars": []interface{}{"name"},
79+
"pattern": "^[a-z]([-a-z0-9]*[a-z0-9])?$",
80+
},
81+
}
82+
83+
if err := validator.Validate(bp, bp.Groups[0].Modules[0], rule, bp.Groups[0], 0); err != nil {
84+
t.Fatalf("unexpected validation error: %v", err)
85+
}
86+
})
87+
88+
t.Run("blueprint_scope_validates_bp_var", func(t *testing.T) {
89+
// blueprint-scoped rule should validate bp.Vars
90+
bp := baseBP
91+
bp.Vars = config.NewDict(map[string]cty.Value{
92+
"global_name": cty.StringVal("Invalid-Name"),
93+
})
94+
95+
rule := modulereader.ValidationRule{
96+
Validator: "regex",
97+
ErrorMessage: "'global_name' must be lowercase and start with a letter.",
98+
Inputs: map[string]interface{}{
99+
"vars": []interface{}{"global_name"},
100+
"pattern": "^[a-z]([-a-z0-9]*[a-z0-9])?$",
101+
"scope": "blueprint",
102+
},
103+
}
104+
105+
err := validator.Validate(bp, bp.Groups[0].Modules[0], rule, bp.Groups[0], 0)
106+
if err == nil {
107+
t.Fatalf("expected blueprint-level validation error, got nil")
108+
}
109+
if !strings.Contains(err.Error(), "must be lowercase") {
110+
t.Fatalf("expected blueprint error to contain message, got: %q", err.Error())
111+
}
112+
})
113+
114+
t.Run("skips_inherited_by_default_and_enforce_inherited", func(t *testing.T) {
115+
// when module.setting equals blueprint var (inherited), default behavior should skip validation
116+
bp := baseBP
117+
// set blueprint var and module setting to the same INVALID value
118+
bp.Vars = config.NewDict(map[string]cty.Value{
119+
"name": cty.StringVal("Invalid-Name"),
120+
})
121+
bp.Groups[0].Modules[0].Settings = config.NewDict(map[string]cty.Value{
122+
"name": cty.StringVal("Invalid-Name"),
123+
})
124+
125+
// rule without enforce_inherited should skip (no error)
126+
ruleSkip := modulereader.ValidationRule{
127+
Validator: "regex",
128+
ErrorMessage: "'name' must be lowercase and start with a letter.",
129+
Inputs: map[string]interface{}{
130+
"vars": []interface{}{"name"},
131+
"pattern": "^[a-z]([-a-z0-9]*[a-z0-9])?$",
132+
},
133+
}
134+
if err := validator.Validate(bp, bp.Groups[0].Modules[0], ruleSkip, bp.Groups[0], 0); err != nil {
135+
t.Fatalf("expected no error when inherited and enforce_inherited absent, got: %v", err)
136+
}
137+
138+
// rule with enforce_inherited should force validation and return error
139+
ruleEnforce := modulereader.ValidationRule{
140+
Validator: "regex",
141+
ErrorMessage: "'name' must be lowercase and start with a letter.",
142+
Inputs: map[string]interface{}{
143+
"vars": []interface{}{"name"},
144+
"pattern": "^[a-z]([-a-z0-9]*[a-z0-9])?$",
145+
"enforce_inherited": true,
146+
},
147+
}
148+
err := validator.Validate(bp, bp.Groups[0].Modules[0], ruleEnforce, bp.Groups[0], 0)
149+
if err == nil {
150+
t.Fatalf("expected error when enforce_inherited is true, got nil")
151+
}
152+
})
153+
154+
t.Run("list_values_are_validated_elementwise", func(t *testing.T) {
155+
// module.setting is a list; validator should validate each element
156+
bp := baseBP
157+
bp.Groups[0].Modules[0].Settings = config.NewDict(map[string]cty.Value{
158+
"tags": cty.ListVal([]cty.Value{
159+
cty.StringVal("good"),
160+
cty.StringVal("BadTag"),
161+
}),
162+
})
163+
164+
rule := modulereader.ValidationRule{
165+
Validator: "regex",
166+
ErrorMessage: "'tags' must be lowercase-only.",
167+
Inputs: map[string]interface{}{
168+
"vars": []interface{}{"tags"},
169+
"pattern": "^[a-z]+$",
170+
},
171+
}
172+
173+
err := validator.Validate(bp, bp.Groups[0].Modules[0], rule, bp.Groups[0], 0)
174+
if err == nil {
175+
t.Fatalf("expected validation error for list containing invalid element, got nil")
176+
}
177+
if !strings.Contains(err.Error(), "must be lowercase-only") {
178+
t.Fatalf("unexpected error message: %q", err.Error())
179+
}
180+
})
64181
}

pkg/validators/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
package validators
1616

1717
// Registry maps validation type strings to their corresponding validator implementation.
18-
var Registry = map[string]Validators{
18+
var Registry = map[string]RuleValidator{
1919
"regex": &RegexValidator{},
2020
}

pkg/validators/validators.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,6 @@ func validators(bp config.Blueprint) []config.Validator {
269269
}
270270

271271
// Validator is the interface that all validation patterns must implement.
272-
type Validators interface {
272+
type RuleValidator interface {
273273
Validate(bp config.Blueprint, mod config.Module, rule modulereader.ValidationRule, group config.Group, modIdx int) error
274274
}

0 commit comments

Comments
 (0)