Skip to content

Commit 5216d92

Browse files
fix
1 parent d804ab9 commit 5216d92

5 files changed

Lines changed: 683 additions & 0 deletions

File tree

linter/config_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package linter_test
22

33
import (
4+
"os"
45
"regexp"
56
"strings"
67
"testing"
@@ -103,6 +104,173 @@ custom_rules:
103104
assert.Equal(t, []string{"./rules/*.ts", "./extra/*.ts"}, config.CustomRules.Paths, "custom_rules.paths should be preserved")
104105
}
105106

107+
func TestLoadConfig_CategorySeverityAliases(t *testing.T) {
108+
t.Parallel()
109+
110+
tests := []struct {
111+
name string
112+
yaml string
113+
expectedSeverity validation.Severity
114+
}{
115+
{
116+
name: "error severity",
117+
yaml: `categories:
118+
style:
119+
severity: error`,
120+
expectedSeverity: validation.SeverityError,
121+
},
122+
{
123+
name: "warn alias for warning",
124+
yaml: `categories:
125+
style:
126+
severity: warn`,
127+
expectedSeverity: validation.SeverityWarning,
128+
},
129+
{
130+
name: "warning severity",
131+
yaml: `categories:
132+
style:
133+
severity: warning`,
134+
expectedSeverity: validation.SeverityWarning,
135+
},
136+
{
137+
name: "hint severity",
138+
yaml: `categories:
139+
style:
140+
severity: hint`,
141+
expectedSeverity: validation.SeverityHint,
142+
},
143+
{
144+
name: "info alias for hint",
145+
yaml: `categories:
146+
style:
147+
severity: info`,
148+
expectedSeverity: validation.SeverityHint,
149+
},
150+
}
151+
152+
for _, tt := range tests {
153+
t.Run(tt.name, func(t *testing.T) {
154+
t.Parallel()
155+
156+
config, err := linter.LoadConfig(strings.NewReader(tt.yaml))
157+
require.NoError(t, err)
158+
require.NotNil(t, config.Categories["style"].Severity, "severity should be set")
159+
assert.Equal(t, tt.expectedSeverity, *config.Categories["style"].Severity, "severity should match expected")
160+
})
161+
}
162+
}
163+
164+
func TestLoadConfig_CategoryEnabled(t *testing.T) {
165+
t.Parallel()
166+
167+
configYAML := `categories:
168+
security:
169+
enabled: false`
170+
config, err := linter.LoadConfig(strings.NewReader(configYAML))
171+
require.NoError(t, err)
172+
require.NotNil(t, config.Categories["security"].Enabled, "enabled should be set")
173+
assert.False(t, *config.Categories["security"].Enabled, "security category should be disabled")
174+
}
175+
176+
func TestLoadConfig_CategoryInvalidSeverity(t *testing.T) {
177+
t.Parallel()
178+
179+
configYAML := `categories:
180+
style:
181+
severity: critical`
182+
_, err := linter.LoadConfig(strings.NewReader(configYAML))
183+
require.Error(t, err)
184+
assert.Contains(t, err.Error(), "unknown severity")
185+
}
186+
187+
func TestLoadConfig_RuleSeverityAliases(t *testing.T) {
188+
t.Parallel()
189+
190+
tests := []struct {
191+
name string
192+
yaml string
193+
expectedSeverity validation.Severity
194+
}{
195+
{
196+
name: "warn alias",
197+
yaml: `rules:
198+
- id: test-rule
199+
severity: warn`,
200+
expectedSeverity: validation.SeverityWarning,
201+
},
202+
{
203+
name: "info alias",
204+
yaml: `rules:
205+
- id: test-rule
206+
severity: info`,
207+
expectedSeverity: validation.SeverityHint,
208+
},
209+
}
210+
211+
for _, tt := range tests {
212+
t.Run(tt.name, func(t *testing.T) {
213+
t.Parallel()
214+
215+
config, err := linter.LoadConfig(strings.NewReader(tt.yaml))
216+
require.NoError(t, err)
217+
require.Len(t, config.Rules, 1)
218+
require.NotNil(t, config.Rules[0].Severity, "severity should be set")
219+
assert.Equal(t, tt.expectedSeverity, *config.Rules[0].Severity, "severity should match expected")
220+
})
221+
}
222+
}
223+
224+
func TestLoadConfig_RuleInvalidSeverity(t *testing.T) {
225+
t.Parallel()
226+
227+
configYAML := `rules:
228+
- id: test-rule
229+
severity: critical`
230+
_, err := linter.LoadConfig(strings.NewReader(configYAML))
231+
require.Error(t, err)
232+
assert.Contains(t, err.Error(), "unknown severity")
233+
}
234+
235+
func TestLoadConfig_ExtendsInvalidType(t *testing.T) {
236+
t.Parallel()
237+
238+
configYAML := `extends:
239+
key: value`
240+
_, err := linter.LoadConfig(strings.NewReader(configYAML))
241+
require.Error(t, err)
242+
assert.Contains(t, err.Error(), "extends must be a string or list of strings")
243+
}
244+
245+
func TestLoadConfig_ExtendsNull(t *testing.T) {
246+
t.Parallel()
247+
248+
configYAML := `extends: null`
249+
config, err := linter.LoadConfig(strings.NewReader(configYAML))
250+
require.NoError(t, err)
251+
assert.Equal(t, []string{"all"}, config.Extends, "null extends should default to all")
252+
}
253+
254+
func TestLoadConfigFromFile_Success(t *testing.T) {
255+
t.Parallel()
256+
257+
tmpFile := t.TempDir() + "/lint.yaml"
258+
err := os.WriteFile(tmpFile, []byte("extends: recommended\n"), 0644)
259+
require.NoError(t, err)
260+
261+
config, err := linter.LoadConfigFromFile(tmpFile)
262+
require.NoError(t, err)
263+
assert.Equal(t, []string{"recommended"}, config.Extends)
264+
}
265+
266+
func TestLoadConfigFromFile_Error(t *testing.T) {
267+
t.Parallel()
268+
269+
_, err := linter.LoadConfigFromFile("/nonexistent/path/lint.yaml")
270+
require.Error(t, err)
271+
assert.Contains(t, err.Error(), "failed to open config file")
272+
}
273+
106274
func TestConfig_ValidateMissingRuleID(t *testing.T) {
107275
t.Parallel()
108276

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package rules_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/speakeasy-api/openapi/linter"
7+
"github.com/speakeasy-api/openapi/openapi"
8+
"github.com/speakeasy-api/openapi/openapi/linter/rules"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
// howToFixer is the interface satisfied by rules that provide fix guidance.
13+
type howToFixer interface {
14+
HowToFix() string
15+
}
16+
17+
// allRules returns every built-in rule instance.
18+
func allRules() []linter.RuleRunner[*openapi.OpenAPI] {
19+
return []linter.RuleRunner[*openapi.OpenAPI]{
20+
&rules.PathParamsRule{},
21+
&rules.PathDeclarationsRule{},
22+
&rules.PathQueryRule{},
23+
&rules.TypedEnumRule{},
24+
&rules.DuplicatedEnumRule{},
25+
&rules.NoEvalInMarkdownRule{},
26+
&rules.OAS3APIServersRule{},
27+
&rules.NoRefSiblingsRule{},
28+
&rules.NoScriptTagsInMarkdownRule{},
29+
&rules.OAS3HostNotExampleRule{},
30+
&rules.OperationIdRule{},
31+
&rules.OperationSuccessResponseRule{},
32+
&rules.OperationErrorResponseRule{},
33+
&rules.OperationTagDefinedRule{},
34+
&rules.OperationSingularTagRule{},
35+
&rules.OperationDescriptionRule{},
36+
&rules.OperationTagsRule{},
37+
&rules.InfoDescriptionRule{},
38+
&rules.InfoContactRule{},
39+
&rules.InfoLicenseRule{},
40+
&rules.LicenseURLRule{},
41+
&rules.PathTrailingSlashRule{},
42+
&rules.PathsKebabCaseRule{},
43+
&rules.NoVerbsInPathRule{},
44+
&rules.TagDescriptionRule{},
45+
&rules.TagsAlphabeticalRule{},
46+
&rules.ContactPropertiesRule{},
47+
&rules.OpenAPITagsRule{},
48+
&rules.ComponentDescriptionRule{},
49+
&rules.UnusedComponentRule{},
50+
&rules.OperationIDValidInURLRule{},
51+
&rules.LinkOperationRule{},
52+
&rules.OAS3HostTrailingSlashRule{},
53+
&rules.OAS3ParameterDescriptionRule{},
54+
&rules.NoAmbiguousPathsRule{},
55+
&rules.DescriptionDuplicationRule{},
56+
&rules.OwaspNoHttpBasicRule{},
57+
&rules.OwaspNoAPIKeysInURLRule{},
58+
&rules.OwaspNoCredentialsInURLRule{},
59+
&rules.OwaspAuthInsecureSchemesRule{},
60+
&rules.OwaspDefineErrorResponses401Rule{},
61+
&rules.OwaspDefineErrorResponses500Rule{},
62+
&rules.OwaspDefineErrorResponses429Rule{},
63+
&rules.OwaspSecurityHostsHttpsOAS3Rule{},
64+
&rules.OwaspDefineErrorValidationRule{},
65+
&rules.OwaspProtectionGlobalUnsafeRule{},
66+
&rules.OwaspProtectionGlobalUnsafeStrictRule{},
67+
&rules.OwaspProtectionGlobalSafeRule{},
68+
&rules.OwaspRateLimitRule{},
69+
&rules.OwaspRateLimitRetryAfterRule{},
70+
&rules.OwaspNoNumericIDsRule{},
71+
&rules.OwaspJWTBestPracticesRule{},
72+
&rules.OwaspArrayLimitRule{},
73+
&rules.OwaspStringLimitRule{},
74+
&rules.OwaspStringRestrictedRule{},
75+
&rules.OwaspIntegerFormatRule{},
76+
&rules.OwaspIntegerLimitRule{},
77+
&rules.OwaspNoAdditionalPropertiesRule{},
78+
&rules.OwaspAdditionalPropertiesConstrainedRule{},
79+
&rules.OAS3NoNullableRule{},
80+
&rules.OAS3ExampleMissingRule{},
81+
&rules.OASSchemaCheckRule{},
82+
}
83+
}
84+
85+
func TestAllRules_MetadataPopulated(t *testing.T) {
86+
t.Parallel()
87+
88+
for _, rule := range allRules() {
89+
t.Run(rule.ID(), func(t *testing.T) {
90+
t.Parallel()
91+
92+
assert.NotEmpty(t, rule.ID(), "rule ID should not be empty")
93+
assert.NotEmpty(t, rule.Category(), "rule category should not be empty")
94+
assert.NotEmpty(t, rule.Description(), "rule description should not be empty")
95+
assert.NotEmpty(t, rule.Summary(), "rule summary should not be empty")
96+
assert.NotEmpty(t, rule.DefaultSeverity(), "rule default severity should not be empty")
97+
98+
if fixer, ok := rule.(howToFixer); ok {
99+
assert.NotEmpty(t, fixer.HowToFix(), "rule HowToFix should not be empty")
100+
}
101+
})
102+
}
103+
}

0 commit comments

Comments
 (0)