Skip to content

Commit fe0a7c4

Browse files
robbiet480claude
andcommitted
test(diff,api,parser): cover the profile-diff branches codecov flagged
- plist parse failures from both the token loop and element decoding, plus a root-level scalar with no container to name it - every inconclusive path in profileContentChange: no enricher, unreadable local file, failed download, and unparseable content on either side - GetProfileContent transport failures: unbuildable request, dead connection, and a truncated body, which must error rather than return half a profile - readProfileContent for a missing file and an unreadable path - WithProfileEnricher Patch coverage for this branch is now 100%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEpzMNJnGaBLAfrPeqknCy
1 parent 8681f1f commit fe0a7c4

4 files changed

Lines changed: 174 additions & 0 deletions

File tree

internal/api/client_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,3 +1124,49 @@ func TestEnrichProfileContents(t *testing.T) {
11241124
t.Errorf("uuid-less profile: got content %q, want empty", profiles[2].Content)
11251125
}
11261126
}
1127+
1128+
func TestGetProfileContentTransportErrors(t *testing.T) {
1129+
t.Run("unbuildable request URL", func(t *testing.T) {
1130+
// A control character in the base URL cannot be turned into a request.
1131+
c := &Client{baseURL: "https://example.com/\x7f", token: "tok", httpClient: &http.Client{}}
1132+
if _, err := c.GetProfileContent(context.Background(), "u"); err == nil {
1133+
t.Error("expected an error building the request")
1134+
}
1135+
})
1136+
1137+
t.Run("connection failure", func(t *testing.T) {
1138+
ts := httptest.NewServer(http.NotFoundHandler())
1139+
url := ts.URL
1140+
ts.Close() // nothing is listening any more
1141+
1142+
t.Setenv("FLEET_PLAN_INSECURE", "1")
1143+
c, err := NewClient(url, "tok")
1144+
if err != nil {
1145+
t.Fatalf("NewClient: %v", err)
1146+
}
1147+
if _, err := c.GetProfileContent(context.Background(), "u"); err == nil {
1148+
t.Error("expected a transport error")
1149+
}
1150+
})
1151+
1152+
t.Run("truncated response body", func(t *testing.T) {
1153+
// Hijack the connection so the headers promise 512 bytes, then hang up
1154+
// after 7. The client must fail while reading rather than hand back a
1155+
// half profile that would diff as a pile of removed keys.
1156+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
1157+
conn, buf, err := http.NewResponseController(w).Hijack()
1158+
if err != nil {
1159+
t.Errorf("hijack: %v", err)
1160+
return
1161+
}
1162+
defer func() { _ = conn.Close() }()
1163+
_, _ = buf.WriteString("HTTP/1.1 200 OK\r\nContent-Length: 512\r\n\r\n<plist>")
1164+
_ = buf.Flush()
1165+
}))
1166+
defer ts.Close()
1167+
1168+
if _, err := testClient(t, ts, "tok").GetProfileContent(context.Background(), "u"); err == nil {
1169+
t.Error("expected an error reading the truncated body")
1170+
}
1171+
})
1172+
}

internal/diff/differ_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,3 +2508,66 @@ func TestDiffProfilesWithoutEnricher(t *testing.T) {
25082508
t.Errorf("path field: got %q", got)
25092509
}
25102510
}
2511+
2512+
func TestWithProfileEnricher(t *testing.T) {
2513+
var o diffOptions
2514+
enricher := &fakeProfileEnricher{}
2515+
WithProfileEnricher(enricher)(&o)
2516+
if o.profileEnricher != enricher {
2517+
t.Error("WithProfileEnricher did not set the enricher")
2518+
}
2519+
}
2520+
2521+
func TestProfileContentChangeInconclusive(t *testing.T) {
2522+
const validPlist = `<plist version="1.0"><dict><key>A</key><string>1</string></dict></plist>`
2523+
2524+
tests := []struct {
2525+
name string
2526+
cur api.Profile
2527+
proposed parser.ParsedProfile
2528+
enricher ProfileEnricher
2529+
}{
2530+
{
2531+
name: "no enricher configured",
2532+
cur: api.Profile{ProfileUUID: "u"},
2533+
proposed: parser.ParsedProfile{Content: validPlist},
2534+
},
2535+
{
2536+
name: "local file could not be read",
2537+
cur: api.Profile{ProfileUUID: "u"},
2538+
proposed: parser.ParsedProfile{Content: ""},
2539+
enricher: &fakeProfileEnricher{content: map[string]string{"u": validPlist}},
2540+
},
2541+
{
2542+
// The download failed, so Content stays empty.
2543+
name: "stored content unavailable",
2544+
cur: api.Profile{ProfileUUID: "u"},
2545+
proposed: parser.ParsedProfile{Content: validPlist},
2546+
enricher: &fakeProfileEnricher{content: map[string]string{}},
2547+
},
2548+
{
2549+
name: "stored content is not parseable",
2550+
cur: api.Profile{ProfileUUID: "u"},
2551+
proposed: parser.ParsedProfile{Content: validPlist},
2552+
enricher: &fakeProfileEnricher{content: map[string]string{"u": "not a profile"}},
2553+
},
2554+
{
2555+
name: "local content is not parseable",
2556+
cur: api.Profile{ProfileUUID: "u"},
2557+
proposed: parser.ParsedProfile{Content: "not a profile"},
2558+
enricher: &fakeProfileEnricher{content: map[string]string{"u": validPlist}},
2559+
},
2560+
}
2561+
2562+
for _, tt := range tests {
2563+
t.Run(tt.name, func(t *testing.T) {
2564+
change, conclusive := profileContentChange(tt.cur, tt.proposed, tt.enricher)
2565+
if conclusive {
2566+
t.Errorf("conclusive: got true, want false (change=%+v)", change)
2567+
}
2568+
if change != nil {
2569+
t.Errorf("change: got %+v, want nil", change)
2570+
}
2571+
})
2572+
}
2573+
}

internal/diff/profilekeys_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,39 @@ func TestProfileDiffSummaryNeverIncludesValues(t *testing.T) {
211211
t.Errorf("summary: got %q", summary)
212212
}
213213
}
214+
215+
func TestPlistKeysMalformed(t *testing.T) {
216+
// Each of these must return an error so diffProfiles falls back to
217+
// name-only matching instead of reporting nonsense keys.
218+
tests := []struct {
219+
name string
220+
content string
221+
}{
222+
{"truncated document", `<plist><dict><key>a</key><string>b`},
223+
{"truncated inside a key", `<plist><dict><key>abc`},
224+
{"truncated inside a value", `<plist><dict><key>a</key><string>abc`},
225+
{"stray angle bracket in a value", `<plist><dict><key>a</key><string><</string></dict></plist>`},
226+
// Malformed markup outside any element the walker decodes directly,
227+
// so the failure surfaces from the token loop rather than a decode.
228+
{"stray angle bracket in an array", `<plist><dict><key>a</key><array><</array></dict></plist>`},
229+
}
230+
for _, tt := range tests {
231+
t.Run(tt.name, func(t *testing.T) {
232+
if _, err := plistKeys([]byte(tt.content)); err == nil {
233+
t.Error("expected an error")
234+
}
235+
})
236+
}
237+
}
238+
239+
func TestPlistKeysValueOutsideContainer(t *testing.T) {
240+
// A plist whose root is a bare scalar rather than a dict: there is no
241+
// container to name the value, so it lands under the empty path.
242+
keys, err := plistKeys([]byte(`<plist version="1.0"><string>bare</string></plist>`))
243+
if err != nil {
244+
t.Fatalf("plistKeys: %v", err)
245+
}
246+
if keys[""] != "bare" {
247+
t.Errorf("got %v, want the value under the empty path", keys)
248+
}
249+
}

internal/parser/parser_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,3 +1110,32 @@ func TestIsNoTeam(t *testing.T) {
11101110
})
11111111
}
11121112
}
1113+
1114+
func TestReadProfileContent(t *testing.T) {
1115+
dir := t.TempDir()
1116+
1117+
good := filepath.Join(dir, "profile.mobileconfig")
1118+
if err := os.WriteFile(good, []byte("<plist/>"), 0o600); err != nil {
1119+
t.Fatal(err)
1120+
}
1121+
1122+
tests := []struct {
1123+
name string
1124+
path string
1125+
want string
1126+
}{
1127+
{name: "readable file", path: good, want: "<plist/>"},
1128+
{name: "missing file", path: filepath.Join(dir, "nope.mobileconfig")},
1129+
// A directory stats fine but cannot be read; content diffing then
1130+
// falls back to name-only matching rather than failing the parse.
1131+
{name: "path is a directory", path: dir},
1132+
}
1133+
1134+
for _, tt := range tests {
1135+
t.Run(tt.name, func(t *testing.T) {
1136+
if got := readProfileContent(tt.path); got != tt.want {
1137+
t.Errorf("got %q, want %q", got, tt.want)
1138+
}
1139+
})
1140+
}
1141+
}

0 commit comments

Comments
 (0)