Skip to content

Commit dd56c07

Browse files
committed
test/fix(schema): more patch coverage; normalise renderRegex whitespace
Codecov measured 95.59% patch coverage on the previous push, just below the 96.07% target. Add targeted tests for the remaining easy branches in expected.go, hint.go, and validate.go: regex unquote failure, half-open upper-bound int range, parseRenderedBounds half-open upper return, and the front-matter-stripped path of docFrontmatterKeyLines. Local patch coverage now reads 97.4% on the changed lines. Address a Copilot review observation on renderRegex line 75: the helper used to strip only the exact prefix `string &` (with a space), so the equivalent CUE form `string&=~"^A$"` fell through to the raw expression. Normalise by stripping `string` then `&` with optional whitespace between them so the renderer recognises both spellings. A new TestRenderExpected_RegexNoWhitespace regresses every variant. https://claude.ai/code/session_01QcXckX3yVwReho2kaq3qRK
1 parent 5336f17 commit dd56c07

4 files changed

Lines changed: 82 additions & 10 deletions

File tree

internal/schema/expected.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,25 @@ func renderStringDisjunction(expr string) (string, bool) {
6161
return "one of: " + strings.Join(literals, ", "), true
6262
}
6363

64-
// renderRegex matches `=~"<pattern>"` (with optional surrounding
65-
// `string &`) and renders it as `string matching <pattern>`.
66-
// Bare regex matchers without a `string &` cover the common
67-
// proto.md shape; constraints that further restrict the type
68-
// fall through to the raw expression.
64+
// renderRegex matches `=~"<pattern>"` (with an optional
65+
// surrounding `string &`) and renders it as `string matching
66+
// <pattern>`. Bare regex matchers without a `string &` cover
67+
// the common proto.md shape; constraints that further restrict
68+
// the type fall through to the raw expression.
69+
//
70+
// CUE doesn't require whitespace around `&`, so both
71+
// `string & =~"^A$"` and `string&=~"^A$"` are valid input.
72+
// We normalise by splitting on the `=~` token directly rather
73+
// than the whitespace-sensitive `string &` prefix, so format
74+
// drift on the input side doesn't lose the rendered shape.
6975
func renderRegex(expr string) (string, bool) {
70-
body := expr
76+
body := strings.TrimSpace(expr)
77+
// Strip an optional `string` prefix and any `&` (with or
78+
// without surrounding whitespace) so we land directly on
79+
// the `=~` token.
80+
body = strings.TrimPrefix(body, "string")
7181
body = strings.TrimSpace(body)
72-
body = strings.TrimPrefix(body, "string &")
82+
body = strings.TrimPrefix(body, "&")
7383
body = strings.TrimSpace(body)
7484
if !strings.HasPrefix(body, "=~") {
7585
return "", false

internal/schema/expected_coverage_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,36 @@ func TestRenderExpected_IntRangeUnknownOperand(t *testing.T) {
7878
got := RenderExpected("int & some-other & >=1")
7979
assert.Equal(t, "int & some-other & >=1", got)
8080
}
81+
82+
// TestRenderExpected_RegexNoWhitespace regresses a Copilot
83+
// review observation: `string&=~"^A$"` (no spaces around `&`)
84+
// is semantically equivalent to `string & =~"^A$"`. Both now
85+
// render as `string matching <pattern>` instead of falling
86+
// through to the raw expression.
87+
func TestRenderExpected_RegexNoWhitespace(t *testing.T) {
88+
cases := map[string]string{
89+
`string&=~"^A$"`: "string matching ^A$",
90+
`string &=~"^B$"`: "string matching ^B$",
91+
`string& =~"^C$"`: "string matching ^C$",
92+
`=~"^D$"`: "string matching ^D$",
93+
}
94+
for in, want := range cases {
95+
assert.Equal(t, want, RenderExpected(in), "input: %q", in)
96+
}
97+
}
98+
99+
// TestRenderExpected_RegexUnquoteFailure covers the
100+
// strconv.Unquote error branch in renderRegex: a pattern
101+
// with a malformed escape sequence (e.g. `\q` which Go does
102+
// not recognise) is rejected after isQuotedString accepts
103+
// the outer quotes.
104+
func TestRenderExpected_RegexUnquoteFailure(t *testing.T) {
105+
assert.Equal(t, `=~"\q"`, RenderExpected(`=~"\q"`))
106+
}
107+
108+
// TestRenderExpected_IntRangeUpperHalfOpen covers the
109+
// half-open upper-bound render branch (`int <= N`) that the
110+
// other tests miss when the lower bound is unspecified.
111+
func TestRenderExpected_IntRangeUpperHalfOpen(t *testing.T) {
112+
assert.Equal(t, "int <= 5", RenderExpected("int & <=5"))
113+
}

internal/schema/hint_coverage_test.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ func TestRenderHint_NonQuotedAlternative(t *testing.T) {
3838
// error branch: a malformed escape inside a string literal
3939
// prevents the literal from being decoded, so no hint fires.
4040
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"))
41+
// `"\q"` uses an invalid Go escape sequence (`\q` is
42+
// not a recognised letter). isQuotedString accepts it
43+
// (it starts and ends with `"`), but strconv.Unquote
44+
// fails, exercising the error branch.
45+
assert.Empty(t, RenderHint(`"ok" | "\q"`, "qq"))
4446
}
4547

4648
// TestRenderHint_ExactMatchSkipped covers the d==0 branch:
@@ -119,6 +121,17 @@ func TestParseRenderedBounds_Edges(t *testing.T) {
119121
assert.False(t, hasLo)
120122
assert.False(t, hasHi)
121123
})
124+
125+
t.Run("half-open upper bound parses cleanly", func(t *testing.T) {
126+
// `int <= 5` is the upper half-open form; the helper
127+
// returns (0, 5, false, true). The other tests use
128+
// `int >= N` which already covers the lower-half
129+
// branch.
130+
_, hi, hasLo, hasHi := parseRenderedBounds("int <= 5")
131+
assert.False(t, hasLo)
132+
assert.True(t, hasHi)
133+
assert.Equal(t, 5, hi)
134+
})
122135
}
123136

124137
// TestLevenshtein_InlineGuardKicksIn exercises the

internal/schema/validate_coverage_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,19 @@ func TestDocFrontmatterKeyLines_SourceFallback(t *testing.T) {
246246
assert.Equal(t, 2, lines["id"])
247247
assert.Equal(t, 3, lines["status"])
248248
}
249+
250+
// TestDocFrontmatterKeyLines_StrippedFrontMatter covers the
251+
// production path: lint.NewFileFromSource(..., true) leaves
252+
// f.FrontMatter populated with the stripped block. The
253+
// helper goes through parseFMBlockKeyLines directly without
254+
// re-extracting from the source.
255+
func TestDocFrontmatterKeyLines_StrippedFrontMatter(t *testing.T) {
256+
src := []byte("---\nid: 1\nstatus: open\n---\n# Body\n")
257+
f, err := lint.NewFileFromSource("doc.md", src, true)
258+
require.NoError(t, err)
259+
require.NotEmpty(t, f.FrontMatter)
260+
lines := docFrontmatterKeyLines(f)
261+
require.NotNil(t, lines)
262+
assert.Equal(t, 2, lines["id"])
263+
assert.Equal(t, 3, lines["status"])
264+
}

0 commit comments

Comments
 (0)