Skip to content

Commit 2e29fff

Browse files
committed
test(checks): cover all auditors (cookies, tls, meta, mixed-content, a11y)
The checks/ package — the actual auditing logic — was at 26.5%; only SecurityHeadersCheck had tests. Added table-driven tests for the other five checkers asserting real Element/Severity findings on both the problem-present and well-configured cases plus edge cases (empty body, nil TLSState, etc.). checks/ 26.5% -> 95.9%; inspect overall 77.9% -> 81.0%. Gate 70 -> 76.
1 parent 0405291 commit 2e29fff

6 files changed

Lines changed: 942 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ jobs:
115115
- name: Coverage threshold
116116
run: |
117117
COVERAGE=$(go tool cover -func=coverage.out | tail -1 | grep -oE '[0-9]+\.[0-9]+' || echo "0")
118-
THRESHOLD=70
118+
THRESHOLD=76
119119
if [ "$(echo "$COVERAGE < $THRESHOLD" | bc -l)" -eq 1 ]; then
120120
echo "::error::Coverage ${COVERAGE}% is below threshold ${THRESHOLD}%"
121121
exit 1

checks/accessibility_test.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package checks
2+
3+
import (
4+
"testing"
5+
6+
"github.com/GrayCodeAI/inspect"
7+
)
8+
9+
func TestAccessibilityCheck_Name(t *testing.T) {
10+
check := &AccessibilityCheck{}
11+
if got := check.Name(); got != "accessibility" {
12+
t.Errorf("Name() = %q, want %q", got, "accessibility")
13+
}
14+
}
15+
16+
// hasFinding reports whether findings contains an entry matching element and severity.
17+
func hasFinding(findings []inspect.Finding, element string, severity inspect.Severity) bool {
18+
for _, f := range findings {
19+
if f.Element == element && f.Severity == severity {
20+
return true
21+
}
22+
}
23+
return false
24+
}
25+
26+
func TestAccessibilityCheck_Run(t *testing.T) {
27+
tests := []struct {
28+
name string
29+
body string
30+
wantElement string
31+
wantSeverity inspect.Severity
32+
wantFinding bool // whether wantElement/wantSeverity should be present
33+
wantCount int // expected total findings; -1 to skip count assertion
34+
}{
35+
{
36+
name: "image without alt is flagged medium",
37+
body: `<html><body><img src="logo.png"></body></html>`,
38+
wantElement: `img[src="logo.png"]`,
39+
wantSeverity: inspect.SeverityMedium,
40+
wantFinding: true,
41+
wantCount: 1,
42+
},
43+
{
44+
name: "image with alt is not flagged",
45+
body: `<html><body><img src="logo.png" alt="Company logo"></body></html>`,
46+
wantFinding: false,
47+
wantCount: 0,
48+
},
49+
{
50+
name: "empty button is flagged medium",
51+
body: `<html><body><button></button></body></html>`,
52+
wantElement: "button",
53+
wantSeverity: inspect.SeverityMedium,
54+
wantFinding: true,
55+
wantCount: 1,
56+
},
57+
{
58+
name: "empty link is flagged medium",
59+
body: `<html><body><a href="/home"></a></body></html>`,
60+
wantElement: "a",
61+
wantSeverity: inspect.SeverityMedium,
62+
wantFinding: true,
63+
wantCount: 1,
64+
},
65+
{
66+
name: "skipped heading level is flagged low",
67+
body: `<html><body><h1>Title</h1><h3>Sub</h3></body></html>`,
68+
wantElement: "h3",
69+
wantSeverity: inspect.SeverityLow,
70+
wantFinding: true,
71+
wantCount: 1,
72+
},
73+
{
74+
name: "missing h1 is flagged medium",
75+
body: `<html><body><h2>First</h2><h3>Second</h3></body></html>`,
76+
wantElement: "h1",
77+
wantSeverity: inspect.SeverityMedium,
78+
wantFinding: true,
79+
wantCount: 1,
80+
},
81+
{
82+
name: "proper heading hierarchy starting at h1 is not flagged",
83+
body: `<html><body><h1>Title</h1><h2>Sub</h2><h3>Deep</h3></body></html>`,
84+
wantFinding: false,
85+
wantCount: 0,
86+
},
87+
{
88+
name: "text input without label is flagged medium",
89+
body: `<html><body><form><input type="text" name="q"></form></body></html>`,
90+
wantElement: "input",
91+
wantSeverity: inspect.SeverityMedium,
92+
wantFinding: true,
93+
wantCount: 1,
94+
},
95+
{
96+
name: "text input with aria-label is not flagged",
97+
body: `<html><body><form><input type="text" name="q" aria-label="Search"></form></body></html>`,
98+
wantFinding: false,
99+
wantCount: 0,
100+
},
101+
{
102+
name: "non-text input type is ignored",
103+
body: `<html><body><form><input type="checkbox" name="agree"></form></body></html>`,
104+
wantFinding: false,
105+
wantCount: 0,
106+
},
107+
{
108+
name: "well-formed accessible page yields no findings",
109+
body: `<html><body>
110+
<h1>Welcome</h1>
111+
<h2>About</h2>
112+
<img src="hero.png" alt="A scenic view">
113+
<a href="/contact">Contact us</a>
114+
<button>Submit</button>
115+
<form><input type="text" name="q" aria-label="Search"></form>
116+
</body></html>`,
117+
wantFinding: false,
118+
wantCount: 0,
119+
},
120+
{
121+
name: "empty body yields no findings",
122+
body: "",
123+
wantFinding: false,
124+
wantCount: 0,
125+
},
126+
{
127+
name: "body with no headings does not trigger missing-h1",
128+
body: `<html><body><p>Just a paragraph.</p></body></html>`,
129+
wantFinding: false,
130+
wantCount: 0,
131+
},
132+
}
133+
134+
check := &AccessibilityCheck{}
135+
for _, tt := range tests {
136+
t.Run(tt.name, func(t *testing.T) {
137+
resp := &Response{
138+
URL: "https://example.com",
139+
Body: []byte(tt.body),
140+
}
141+
findings := check.Run(resp)
142+
143+
if tt.wantCount >= 0 && len(findings) != tt.wantCount {
144+
t.Errorf("got %d findings, want %d: %+v", len(findings), tt.wantCount, findings)
145+
}
146+
147+
if tt.wantFinding {
148+
if !hasFinding(findings, tt.wantElement, tt.wantSeverity) {
149+
t.Errorf("expected finding with Element=%q Severity=%v, got %+v",
150+
tt.wantElement, tt.wantSeverity, findings)
151+
}
152+
}
153+
154+
// Every emitted finding must be attributed to this check and carry the URL.
155+
for _, f := range findings {
156+
if f.Check != "accessibility" {
157+
t.Errorf("finding Check = %q, want %q", f.Check, "accessibility")
158+
}
159+
if f.URL != resp.URL {
160+
t.Errorf("finding URL = %q, want %q", f.URL, resp.URL)
161+
}
162+
}
163+
})
164+
}
165+
}
166+
167+
// TestAccessibilityCheck_NilTLSAndHeaders verifies the check ignores
168+
// transport-level fields (nil TLSState, nil Headers) and only reads Body.
169+
func TestAccessibilityCheck_NilTLSAndHeaders(t *testing.T) {
170+
check := &AccessibilityCheck{}
171+
resp := &Response{
172+
URL: "https://example.com",
173+
Body: []byte(`<img src="x.png">`),
174+
TLSState: nil,
175+
Headers: nil,
176+
}
177+
findings := check.Run(resp)
178+
if !hasFinding(findings, `img[src="x.png"]`, inspect.SeverityMedium) {
179+
t.Errorf("expected image-without-alt finding regardless of nil TLS/Headers, got %+v", findings)
180+
}
181+
}
182+
183+
// TestAccessibilityCheck_MultipleImages confirms one finding per offending image.
184+
func TestAccessibilityCheck_MultipleImages(t *testing.T) {
185+
check := &AccessibilityCheck{}
186+
resp := &Response{
187+
URL: "https://example.com",
188+
Body: []byte(`<img src="a.png"><img src="b.png" alt="ok"><img src="c.png">`),
189+
}
190+
findings := check.Run(resp)
191+
if len(findings) != 2 {
192+
t.Fatalf("expected 2 findings (a.png, c.png), got %d: %+v", len(findings), findings)
193+
}
194+
if !hasFinding(findings, `img[src="a.png"]`, inspect.SeverityMedium) {
195+
t.Error("missing finding for a.png")
196+
}
197+
if !hasFinding(findings, `img[src="c.png"]`, inspect.SeverityMedium) {
198+
t.Error("missing finding for c.png")
199+
}
200+
}

0 commit comments

Comments
 (0)