Skip to content

Commit 3956e83

Browse files
test(cli): pin every isTimeoutErr branch for 100% patch coverage
The diff-cover gate flagged cmd/monitor.go 297-298 (nil-error guard) and 304-305 (net.Error Timeout()==true branch) as uncovered: the existing provision-timeout regression test only reaches the errors.Is(DeadlineExceeded) path because http.Client.Timeout errors match DeadlineExceeded first. Add TestIsTimeoutErr — a table-driven unit test with a synthetic net.Error (fakeNetTimeoutErr, message via named const) covering nil, bare/wrapped DeadlineExceeded, bare/wrapped timeout net.Error, non-timeout net.Error, and plain errors. isTimeoutErr is now 100% covered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 78b8501 commit 3956e83

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

‎cmd/monitor_test.go‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ package cmd
66

77
import (
88
"bytes"
9+
"context"
10+
"errors"
11+
"fmt"
12+
"net"
913
"net/http"
1014
"net/http/httptest"
1115
"strings"
@@ -140,3 +144,47 @@ func withTestAPI(t *testing.T, baseURL string) {
140144
HTTPClient = prevClient
141145
})
142146
}
147+
148+
// ── isTimeoutErr unit coverage ────────────────────────────────────────────────
149+
150+
// fakeNetTimeoutErrMsg is the message carried by fakeNetTimeoutErr; named so
151+
// tests don't scatter string literals.
152+
const fakeNetTimeoutErrMsg = "dial tcp: i/o timeout (synthetic)"
153+
154+
// fakeNetTimeoutErr is a minimal net.Error with a configurable Timeout().
155+
// It deliberately does NOT wrap context.DeadlineExceeded, so it exercises the
156+
// errors.As(net.Error) branch of isTimeoutErr rather than the errors.Is one
157+
// (an http.Client.Timeout error matches DeadlineExceeded first, leaving the
158+
// net.Error branch unreachable through provisionResource alone).
159+
type fakeNetTimeoutErr struct{ timeout bool }
160+
161+
func (e fakeNetTimeoutErr) Error() string { return fakeNetTimeoutErrMsg }
162+
func (e fakeNetTimeoutErr) Timeout() bool { return e.timeout }
163+
func (e fakeNetTimeoutErr) Temporary() bool { return e.timeout }
164+
165+
// TestIsTimeoutErr pins every branch of isTimeoutErr: the nil guard, the
166+
// context.DeadlineExceeded path, the net.Error-with-Timeout() path (including
167+
// when wrapped, as *url.Error does), and the non-timeout fallthrough.
168+
func TestIsTimeoutErr(t *testing.T) {
169+
// Compile-time proof the fake satisfies net.Error.
170+
var _ net.Error = fakeNetTimeoutErr{}
171+
172+
cases := []struct {
173+
name string
174+
err error
175+
want bool
176+
}{
177+
{"nil error is not a timeout", nil, false},
178+
{"context deadline exceeded", context.DeadlineExceeded, true},
179+
{"wrapped context deadline exceeded", fmt.Errorf("post: %w", context.DeadlineExceeded), true},
180+
{"net.Error with Timeout()==true", fakeNetTimeoutErr{timeout: true}, true},
181+
{"wrapped net.Error with Timeout()==true", fmt.Errorf("post: %w", fakeNetTimeoutErr{timeout: true}), true},
182+
{"net.Error with Timeout()==false", fakeNetTimeoutErr{timeout: false}, false},
183+
{"plain non-timeout error", errors.New("connection refused"), false},
184+
}
185+
for _, tc := range cases {
186+
t.Run(tc.name, func(t *testing.T) {
187+
assert.Equal(t, tc.want, isTimeoutErr(tc.err))
188+
})
189+
}
190+
}

0 commit comments

Comments
 (0)