Skip to content

Commit cc44d63

Browse files
test: raise coverage over 95% to clear the patch-coverage gate (#14)
Adds branch-level unit tests for the cheapest previously-uncovered paths across all four packages, lifting cmd 78.1%->82.0%, cliconfig 76.6%->81.2%, tokens 90.8%->92.3% (statement coverage; codecov line coverage clears 95%). - cmd/coverage_units_test.go: ExitCodeError nil/zero receivers, withExitCode nil pass-through, errAuthRequired empty-detail default, parseAPIError 402/429/5xx/4xx + empty/non-JSON/legacy-upgrade branches, classifyError DNS/OpError/generic-url/session-phrase arms, parseResourceFilters + matchResourceFilters + lower/eqFold, deploy_stub mcpAliasFor/curlHintFor default arms. - cmd/coverage_login_test.go: createCLISession + pollForAuthCompletion success/error/malformed branches via httptest (no real network); up.go truncate/apiResourceType/shortToken/webhookReceiveURL. - internal/cliconfig/coverage_test.go: SecretBackendName branches, Load resolution from secretstore/fallback/parse-error, Save logout-clears-store, Clear idempotency. - internal/tokens/coverage_test.go: Load existing-file round-trip + parse error. All CI-safe (httptest + temp HOME + in-memory secret backend). Existing suite stays green under -race. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 607c0fd commit cc44d63

4 files changed

Lines changed: 671 additions & 0 deletions

File tree

cmd/coverage_login_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package cmd
2+
3+
// coverage_login_test.go — drives the login HTTP helpers (createCLISession,
4+
// pollForAuthCompletion) through an httptest server so their success, error,
5+
// and malformed-response branches are covered without real network access.
6+
// All servers respond immediately (HTTP 200), so no test waits on the 2s
7+
// pollInterval — only the happy-path / error branches that return on the
8+
// first iteration are exercised.
9+
10+
import (
11+
"net/http"
12+
"net/http/httptest"
13+
"strings"
14+
"testing"
15+
)
16+
17+
func TestCreateCLISession(t *testing.T) {
18+
// Success: server returns a valid session.
19+
t.Run("ok", func(t *testing.T) {
20+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21+
if r.URL.Path != "/auth/cli" {
22+
t.Errorf("path = %s", r.URL.Path)
23+
}
24+
w.WriteHeader(http.StatusCreated)
25+
_, _ = w.Write([]byte(`{"session_id":"sess_1","auth_url":"https://x/login"}`))
26+
}))
27+
defer srv.Close()
28+
withTestAPI(t, srv.URL)
29+
30+
s, err := createCLISession([]string{"tok1"})
31+
if err != nil || s.SessionID != "sess_1" || s.AuthURL != "https://x/login" {
32+
t.Fatalf("createCLISession = %+v / %v", s, err)
33+
}
34+
})
35+
36+
// Non-2xx status surfaces a server-returned error.
37+
t.Run("error_status", func(t *testing.T) {
38+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39+
w.WriteHeader(http.StatusInternalServerError)
40+
_, _ = w.Write([]byte("boom"))
41+
}))
42+
defer srv.Close()
43+
withTestAPI(t, srv.URL)
44+
45+
if _, err := createCLISession(nil); err == nil || !strings.Contains(err.Error(), "500") {
46+
t.Fatalf("expected 500 error, got %v", err)
47+
}
48+
})
49+
50+
// Malformed JSON body.
51+
t.Run("bad_json", func(t *testing.T) {
52+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
53+
_, _ = w.Write([]byte("not json"))
54+
}))
55+
defer srv.Close()
56+
withTestAPI(t, srv.URL)
57+
58+
if _, err := createCLISession(nil); err == nil || !strings.Contains(err.Error(), "parsing session") {
59+
t.Fatalf("expected parse error, got %v", err)
60+
}
61+
})
62+
63+
// Valid JSON but missing required fields.
64+
t.Run("incomplete", func(t *testing.T) {
65+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+
_, _ = w.Write([]byte(`{"session_id":""}`))
67+
}))
68+
defer srv.Close()
69+
withTestAPI(t, srv.URL)
70+
71+
if _, err := createCLISession(nil); err == nil || !strings.Contains(err.Error(), "invalid session") {
72+
t.Fatalf("expected invalid-session error, got %v", err)
73+
}
74+
})
75+
}
76+
77+
func TestPollForAuthCompletion(t *testing.T) {
78+
// Immediate success on the first poll.
79+
t.Run("ok", func(t *testing.T) {
80+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81+
w.WriteHeader(http.StatusOK)
82+
_, _ = w.Write([]byte(`{"api_key":"sk_live","email":"a@b.c","tier":"hobby"}`))
83+
}))
84+
defer srv.Close()
85+
withTestAPI(t, srv.URL)
86+
87+
res, err := pollForAuthCompletion("sess_1")
88+
if err != nil || res.APIKey != "sk_live" {
89+
t.Fatalf("poll = %+v / %v", res, err)
90+
}
91+
})
92+
93+
// 200 but no API key -> error.
94+
t.Run("no_key", func(t *testing.T) {
95+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
96+
w.WriteHeader(http.StatusOK)
97+
_, _ = w.Write([]byte(`{"api_key":""}`))
98+
}))
99+
defer srv.Close()
100+
withTestAPI(t, srv.URL)
101+
102+
if _, err := pollForAuthCompletion("s"); err == nil || !strings.Contains(err.Error(), "no API key") {
103+
t.Fatalf("expected no-key error, got %v", err)
104+
}
105+
})
106+
107+
// 200 but malformed JSON -> parse error.
108+
t.Run("bad_json", func(t *testing.T) {
109+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
110+
w.WriteHeader(http.StatusOK)
111+
_, _ = w.Write([]byte("xx"))
112+
}))
113+
defer srv.Close()
114+
withTestAPI(t, srv.URL)
115+
116+
if _, err := pollForAuthCompletion("s"); err == nil || !strings.Contains(err.Error(), "parsing auth result") {
117+
t.Fatalf("expected parse error, got %v", err)
118+
}
119+
})
120+
121+
// Unexpected status (not 200/202) returns immediately with an error.
122+
t.Run("unexpected_status", func(t *testing.T) {
123+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124+
w.WriteHeader(http.StatusForbidden)
125+
_, _ = w.Write([]byte("nope"))
126+
}))
127+
defer srv.Close()
128+
withTestAPI(t, srv.URL)
129+
130+
if _, err := pollForAuthCompletion("s"); err == nil || !strings.Contains(err.Error(), "unexpected status 403") {
131+
t.Fatalf("expected 403 error, got %v", err)
132+
}
133+
})
134+
}
135+
136+
// TestUpHelpers covers the small pure helpers in up.go that the integration
137+
// flow doesn't fully exercise.
138+
func TestUpHelpers(t *testing.T) {
139+
// truncate: under-limit returns unchanged, over-limit clamps + ellipsis.
140+
if got := truncate("short", 10); got != "short" {
141+
t.Errorf("truncate under = %q", got)
142+
}
143+
if got := truncate("abcdefghij", 3); got != "abc…" {
144+
t.Errorf("truncate over = %q", got)
145+
}
146+
147+
// apiResourceType: known types pass through lowercased; unknown also
148+
// returns lowercased trimmed (single-site mapping seam).
149+
if got := apiResourceType(" Postgres "); got != "postgres" {
150+
t.Errorf("known type = %q", got)
151+
}
152+
if got := apiResourceType("MONGODB"); got != "mongodb" {
153+
t.Errorf("mongodb = %q", got)
154+
}
155+
if got := apiResourceType("Custom"); got != "custom" {
156+
t.Errorf("unknown type = %q", got)
157+
}
158+
159+
// shortToken + webhookReceiveURL exercise the token-derivation seams.
160+
if got := shortToken("abcdefghijkl"); got != "abcdefgh" {
161+
t.Errorf("shortToken = %q", got)
162+
}
163+
withTestAPI(t, "https://api.example.com/")
164+
if got := webhookReceiveURL("tok_9"); got != "https://api.example.com/webhook/receive/tok_9" {
165+
t.Errorf("webhookReceiveURL = %q", got)
166+
}
167+
}

0 commit comments

Comments
 (0)