Skip to content

Commit 5336f17

Browse files
committed
test(schema): cover edge branches for codecov patch gate
The PR's codecov/patch check failed at 85.86% (target 96.07%) because several edge branches in the new diagnostic / expected-value / hint code lacked tests: - diagnostic.go: no-Actual / field-only Format() paths; formatActual's json.Marshal fallback for unmarshalable values - expected.go: empty input, non-quoted disjunction alternative, regex unquote failure, mixed-exclusive int range, missing int keyword, unknown operand - hint.go: non-string actual, single-part disjunction, unquote failure, exact-match skip, int / int64 actuals, parseRenderedBounds malformed-input branches, levenshtein empty / over-cap fallback, runeCountAtMost / tooLong helpers - validate.go: schemaKeyForPath / lookupConstraint / lookupFM / schemaRef edge cases; json.Marshal failure path; extra-field branch; docFrontmatterKeyLines source- fallback for files built via lint.NewFile (the integration runner's mode) Patch coverage on the changed lines climbs from 85.86% to ~96.3%, satisfying the codecov/patch gate. https://claude.ai/code/session_01QcXckX3yVwReho2kaq3qRK
1 parent a87ddee commit 5336f17

4 files changed

Lines changed: 564 additions & 0 deletions

File tree

internal/schema/diagnostic_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,42 @@ func TestSchemaDiagnostic_Format_AllFields(t *testing.T) {
3232
assert.Equal(t, want, got)
3333
}
3434

35+
// TestSchemaDiagnostic_Format_NoActual covers the branch
36+
// where Field and Expected are set but Actual is empty: the
37+
// rendered message uses ": expected ..." rather than the
38+
// "got X, expected Y" form, so absent-but-known constraints
39+
// (e.g. a wildcard-text scope) read cleanly.
40+
func TestSchemaDiagnostic_Format_NoActual(t *testing.T) {
41+
d := SchemaDiagnostic{
42+
Field: "## Goal",
43+
Expected: "section to be present",
44+
SchemaRef: "kind plan",
45+
}
46+
want := "## Goal: expected section to be present\nschema: kind plan"
47+
assert.Equal(t, want, d.Format())
48+
}
49+
50+
// TestSchemaDiagnostic_Format_FieldOnly covers the very
51+
// minimal render: just a field name with no actual,
52+
// expected, hint, or schema ref. The renderer should still
53+
// produce a non-empty string.
54+
func TestSchemaDiagnostic_Format_FieldOnly(t *testing.T) {
55+
d := SchemaDiagnostic{Field: "field"}
56+
assert.Equal(t, "field", d.Format())
57+
}
58+
59+
// TestFormatActual_FallbackOnMarshalError exercises the
60+
// final fmt.Sprintf("%v", v) fallback. json.Marshal fails
61+
// on channels, functions, and complex values; the helper
62+
// degrades to the default formatter so the caller still
63+
// gets a printable string.
64+
func TestFormatActual_FallbackOnMarshalError(t *testing.T) {
65+
// Channels can't be JSON-marshalled; the helper falls
66+
// through to %v which produces a pointer-shaped string.
67+
got := formatActual(make(chan int))
68+
assert.NotEmpty(t, got)
69+
}
70+
3571
// TestSchemaDiagnostic_Format_NoHint covers the no-hint branch:
3672
// when the extractor cannot suggest a fix, the message ends at the
3773
// expected line and goes straight to the schema reference.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package schema
2+
3+
import (
4+
"math"
5+
"strconv"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
// TestRenderExpected_Edges exercises the early-return branches
12+
// in RenderExpected and renderStringDisjunction / renderRegex /
13+
// renderNonEmptyString that previously had no test coverage.
14+
func TestRenderExpected_Edges(t *testing.T) {
15+
t.Run("empty input returns empty", func(t *testing.T) {
16+
assert.Equal(t, "", RenderExpected(""))
17+
// Whitespace-only is trimmed to "" before any shape
18+
// matches; the renderer returns the trimmed value.
19+
assert.Equal(t, "", RenderExpected(" "))
20+
})
21+
22+
t.Run("disjunction with non-quoted alternative falls back", func(t *testing.T) {
23+
// `int | "x"` has a non-string-literal alternative; the
24+
// renderer should fall back to the raw expression
25+
// rather than misreport.
26+
assert.Equal(t, `int | "x"`, RenderExpected(`int | "x"`))
27+
})
28+
29+
t.Run("regex with bad unquote falls back", func(t *testing.T) {
30+
// A regex pattern that uses single-quote delimiters
31+
// (CUE accepts double-quoted only) can't be unquoted;
32+
// the renderer falls through to the raw form.
33+
assert.Equal(t, `=~'foo'`, RenderExpected(`=~'foo'`))
34+
})
35+
36+
t.Run("regex without quoted pattern falls back", func(t *testing.T) {
37+
assert.Equal(t, `=~foo`, RenderExpected(`=~foo`))
38+
})
39+
}
40+
41+
// TestRenderExpected_IntRangeMixed covers the mixed-exclusive
42+
// render branch: when one bound can be converted to inclusive
43+
// (`<= N-1`) but the other can't (`>MaxInt`), the renderer
44+
// emits both with explicit comparison operators so the
45+
// asymmetry stays visible.
46+
func TestRenderExpected_IntRangeMixed(t *testing.T) {
47+
maxStr := strconv.Itoa(math.MaxInt)
48+
got := RenderExpected("int & >" + maxStr + " & <=10")
49+
assert.Contains(t, got, "int > "+maxStr)
50+
assert.Contains(t, got, "and <= 10")
51+
}
52+
53+
// TestRenderExpected_IntRangeOnlyExclusiveUpper exercises the
54+
// half-open upper-bound rendering with an exclusive `<` form
55+
// that the overflow guard preserves intact.
56+
func TestRenderExpected_IntRangeOnlyExclusiveUpper(t *testing.T) {
57+
minStr := strconv.Itoa(math.MinInt)
58+
got := RenderExpected("int & <" + minStr)
59+
assert.Equal(t, "int < "+minStr, got)
60+
}
61+
62+
// TestRenderExpected_IntRangeNoIntKeyword regresses the early
63+
// false return when the `int` keyword is missing from the
64+
// constraint (e.g. `>=1 & <=5` without `int &`). The
65+
// constraint isn't recognised as an int range, so the renderer
66+
// falls back to the raw expression.
67+
func TestRenderExpected_IntRangeNoIntKeyword(t *testing.T) {
68+
got := RenderExpected(">=1 & <=5")
69+
assert.Equal(t, ">=1 & <=5", got)
70+
}
71+
72+
// TestRenderExpected_IntRangeUnknownOperand exercises the
73+
// fallback when a `&`-joined part doesn't fit the small
74+
// grammar (int / >=, >, <=, <). The renderer aborts and falls
75+
// back to the raw expression so a partial constraint never
76+
// reaches the user.
77+
func TestRenderExpected_IntRangeUnknownOperand(t *testing.T) {
78+
got := RenderExpected("int & some-other & >=1")
79+
assert.Equal(t, "int & some-other & >=1", got)
80+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package schema
2+
3+
import (
4+
"math"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
// TestRenderHint_NonStringActualDisjunction exercises the
12+
// `actual` non-string branch in hintForStringDisjunction: a
13+
// numeric actual against a string-disjunction constraint
14+
// produces no hint (the disjunction path only fires for
15+
// string actuals).
16+
func TestRenderHint_NonStringActualDisjunction(t *testing.T) {
17+
expr := `"a" | "b"`
18+
assert.Empty(t, RenderHint(expr, 42))
19+
assert.Empty(t, RenderHint(expr, float64(3.14)))
20+
}
21+
22+
// TestRenderHint_SinglePartDisjunction covers the "not a
23+
// disjunction" early return (len(parts) < 2): a bare `"x"`
24+
// constraint cannot produce a "did you mean" hint.
25+
func TestRenderHint_SinglePartDisjunction(t *testing.T) {
26+
assert.Empty(t, RenderHint(`"x"`, "y"))
27+
}
28+
29+
// TestRenderHint_NonQuotedAlternative covers the
30+
// isQuotedString=false branch: when one of the disjunction
31+
// alternatives isn't a string literal (e.g. `int | "x"`), the
32+
// extractor backs off rather than misreporting.
33+
func TestRenderHint_NonQuotedAlternative(t *testing.T) {
34+
assert.Empty(t, RenderHint(`int | "x"`, "y"))
35+
}
36+
37+
// TestRenderHint_UnquoteFailure covers the strconv.Unquote
38+
// error branch: a malformed escape inside a string literal
39+
// prevents the literal from being decoded, so no hint fires.
40+
func TestRenderHint_UnquoteFailure(t *testing.T) {
41+
// Two alternatives so len(parts) >= 2; the bad escape
42+
// breaks Unquote on the second literal.
43+
assert.Empty(t, RenderHint(`"ok" | "\xff"`, "ok-typo"))
44+
}
45+
46+
// TestRenderHint_ExactMatchSkipped covers the d==0 branch:
47+
// the actual exactly equals one of the literals, so no hint
48+
// fires (a "did you mean X?" message when X equals the actual
49+
// would be confusing).
50+
func TestRenderHint_ExactMatchSkipped(t *testing.T) {
51+
// The CUE constraint validator never calls RenderHint on
52+
// a value that satisfies the constraint, but the
53+
// disjunction extractor still guards against the case
54+
// defensively. Pass an exact match to exercise the
55+
// guard.
56+
assert.Empty(t, RenderHint(`"foo" | "bar"`, "foo"))
57+
}
58+
59+
// TestRenderHint_IntRangeNonNumericActual exercises the
60+
// toFloat64=false branch: a string actual against an int
61+
// range produces no hint.
62+
func TestRenderHint_IntRangeNonNumericActual(t *testing.T) {
63+
assert.Empty(t, RenderHint("int & >=1 & <=5", "not-a-number"))
64+
}
65+
66+
// TestRenderHint_IntRangeWithIntActual exercises the int /
67+
// int64 paths of toFloat64 that the float64 default missed.
68+
func TestRenderHint_IntRangeWithIntActual(t *testing.T) {
69+
assert.Equal(t, "try 1", RenderHint("int & >=1 & <=5", int(0)))
70+
assert.Equal(t, "try 5", RenderHint("int & >=1 & <=5", int64(6)))
71+
}
72+
73+
// TestRenderHint_IntRangeNoBoundCrossed covers the no-hint
74+
// branch when neither bound is exceeded but the actual is
75+
// still in range (in-range actuals already exit before hint
76+
// rendering, but the guard inside hintForIntRange covers
77+
// boundary-equal cases).
78+
func TestRenderHint_IntRangeNoBoundCrossed(t *testing.T) {
79+
// Value equal to a bound — within range, no hint.
80+
assert.Empty(t, RenderHint("int & >=1 & <=5", float64(1)))
81+
assert.Empty(t, RenderHint("int & >=1 & <=5", float64(5)))
82+
}
83+
84+
// TestParseRenderedBounds_Edges exercises the error branches
85+
// in parseRenderedBounds. These fire when the rendered string
86+
// is malformed; in practice renderIntRange produces a
87+
// well-formed string, so hintForIntRange short-circuits on
88+
// the renderIntRange ok=false path. Direct calls cover the
89+
// defensive branches for future callers.
90+
func TestParseRenderedBounds_Edges(t *testing.T) {
91+
t.Run("malformed between drops the and separator", func(t *testing.T) {
92+
_, _, hasLo, hasHi := parseRenderedBounds("int between 1 5")
93+
assert.False(t, hasLo)
94+
assert.False(t, hasHi)
95+
})
96+
97+
t.Run("non-integer lower bound", func(t *testing.T) {
98+
_, _, hasLo, _ := parseRenderedBounds("int between abc and 5")
99+
assert.False(t, hasLo)
100+
})
101+
102+
t.Run("non-integer upper bound", func(t *testing.T) {
103+
_, _, _, hasHi := parseRenderedBounds("int between 1 and zzz")
104+
assert.False(t, hasHi)
105+
})
106+
107+
t.Run("non-integer half-open lower", func(t *testing.T) {
108+
_, _, hasLo, _ := parseRenderedBounds("int >= xyz")
109+
assert.False(t, hasLo)
110+
})
111+
112+
t.Run("non-integer half-open upper", func(t *testing.T) {
113+
_, _, _, hasHi := parseRenderedBounds("int <= xyz")
114+
assert.False(t, hasHi)
115+
})
116+
117+
t.Run("unknown rendered form", func(t *testing.T) {
118+
_, _, hasLo, hasHi := parseRenderedBounds("string")
119+
assert.False(t, hasLo)
120+
assert.False(t, hasHi)
121+
})
122+
}
123+
124+
// TestLevenshtein_InlineGuardKicksIn exercises the
125+
// CodeQL-visible inline guard path inside levenshtein() that
126+
// the over-cap helper would normally short-circuit before.
127+
// The test calls levenshtein directly with strings sized at
128+
// the cap to confirm the inner branch path is reached.
129+
func TestLevenshtein_InlineGuardKicksIn(t *testing.T) {
130+
// Inputs at maxLevInput rune count stay inside the DP
131+
// branch; smaller of the two governs the row size.
132+
short := strings.Repeat("a", maxLevInput)
133+
tiny := "abc"
134+
// short ↔ tiny: 1024 - 3 = 1021 deletions, 0 substitutions
135+
// from the 3-char overlap, so distance ~ 1021.
136+
got := levenshtein(short, tiny)
137+
assert.Greater(t, got, 1000)
138+
}
139+
140+
// TestLevenshtein_OneEmpty exercises the early-return branches
141+
// for empty operands that the typo tests don't hit.
142+
func TestLevenshtein_OneEmpty(t *testing.T) {
143+
assert.Equal(t, 4, levenshtein("", "abcd"))
144+
assert.Equal(t, 4, levenshtein("abcd", ""))
145+
}
146+
147+
// TestLevenshtein_BothOverCapReturnsLonger exercises both
148+
// branches of the over-cap fallback: when b is longer than a,
149+
// the helper returns len(b)'s capped count; when a is
150+
// longer, it returns len(a). The first call below covers
151+
// the `return cb` branch the typo tests miss.
152+
func TestLevenshtein_BothOverCapReturnsLonger(t *testing.T) {
153+
short := strings.Repeat("a", maxLevInput+5)
154+
longer := strings.Repeat("a", maxLevInput+50)
155+
// b longer → returns cb (= maxLevInput+1).
156+
got := levenshtein(short, longer)
157+
assert.Equal(t, maxLevInput+1, got)
158+
// a longer → returns ca.
159+
got = levenshtein(longer, short)
160+
assert.Equal(t, maxLevInput+1, got)
161+
}
162+
163+
// TestRuneCountAtMost_Cap exercises the early-exit branch of
164+
// runeCountAtMost: a string longer than the cap returns
165+
// exactly the cap, not the true rune count.
166+
func TestRuneCountAtMost_Cap(t *testing.T) {
167+
s := strings.Repeat("a", maxLevInput+10)
168+
got := runeCountAtMost(s, maxLevInput)
169+
assert.Equal(t, maxLevInput, got)
170+
}
171+
172+
// TestTooLongForLevInput_Bound exercises the boundary branch:
173+
// exactly maxLevInput runes is not "too long", maxLevInput+1
174+
// is.
175+
func TestTooLongForLevInput_Bound(t *testing.T) {
176+
exact := strings.Repeat("a", maxLevInput)
177+
tooLong := strings.Repeat("a", maxLevInput+1)
178+
assert.False(t, tooLongForLevInput(exact))
179+
assert.True(t, tooLongForLevInput(tooLong))
180+
}
181+
182+
// TestRenderHint_IntRangeOverflowFalsePositive guards the
183+
// hint extractor against suggesting the rendered exclusive
184+
// form's bound (`int > MaxInt`) for an in-range value. With
185+
// the inclusive shift skipped, parseRenderedBounds doesn't
186+
// match the rendered form, so hintForIntRange returns no
187+
// hint.
188+
func TestRenderHint_IntRangeOverflowFalsePositive(t *testing.T) {
189+
maxStr := math.MaxInt
190+
assert.Empty(t, RenderHint("int & >"+itoa(maxStr), float64(0)))
191+
}
192+
193+
// itoa is a tiny wrapper so the test reads naturally without
194+
// importing strconv at the call site.
195+
func itoa(n int) string {
196+
if n == math.MaxInt {
197+
return "9223372036854775807"
198+
}
199+
return ""
200+
}

0 commit comments

Comments
 (0)