Skip to content

Commit 571ed38

Browse files
test(coverage/logctx): drive logctx to ≥95% (was 87.1%)
Adds tests for the uncovered branches in common/logctx: - WithGroup on the wrapper handler (was 0% — entire function uncovered) now exercised via TestHandler_WithGroupPreservesService, which also asserts that service + commit_id continue to be injected on records emitted through a grouped child handler. - The `if ctx == nil` guards in WithTraceID / WithTID / WithTeamID (each 66.7% — only the happy path was hit) covered by TestKeys_WithSettersAcceptNilCtx, which passes an explicitly nil context to each setter and round-trips through the matching getter. Result: logctx coverage 87.1% → 100.0%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5cb47ae commit 571ed38

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

logctx/handler_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,69 @@ func TestHandler_WithAttrsPreservesService(t *testing.T) {
181181
t.Errorf("WithAttrs dropped extra attr")
182182
}
183183
}
184+
185+
// TestHandler_WithGroupPreservesService confirms that WithGroup returns a
186+
// wrapper that still injects service + commit_id, and that the underlying
187+
// base handler's grouping behaviour is preserved — every subsequent attr
188+
// lands under the named group. Without this guard a refactor that drops
189+
// the field copy would silently emit log lines without service / commit_id
190+
// once any caller used slog.Logger.WithGroup.
191+
func TestHandler_WithGroupPreservesService(t *testing.T) {
192+
buf, h := newTestHandler(t, "api")
193+
child := h.WithGroup("grp")
194+
if err := child.Handle(context.Background(), newRecord("hi")); err != nil {
195+
t.Fatalf("Handle: %v", err)
196+
}
197+
rec := decode(t, buf)
198+
// service + commit_id are added by the wrapper AFTER the group is
199+
// active on the base, so they land under "grp" in JSON output. That
200+
// is the documented behaviour for slog.Handler.WithGroup composition.
201+
grp, ok := rec["grp"].(map[string]any)
202+
if !ok {
203+
t.Fatalf("expected nested grp object, got %v", rec)
204+
}
205+
if grp[FieldService] != "api" {
206+
t.Errorf("WithGroup dropped service: %v", grp[FieldService])
207+
}
208+
if grp[FieldCommitID] == nil {
209+
t.Errorf("WithGroup dropped commit_id")
210+
}
211+
}
212+
213+
// TestKeys_WithSettersAcceptNilCtx covers the `if ctx == nil` branch in
214+
// each of WithTraceID / WithTID / WithTeamID. These branches exist to
215+
// guard against callers passing nil — slog.Logger.Log historically did
216+
// this when the user code passed a nil context — and the function must
217+
// internally fall back to context.Background rather than panic on
218+
// context.WithValue(nil, …).
219+
func TestKeys_WithSettersAcceptNilCtx(t *testing.T) {
220+
// Each setter must not panic on a nil ctx, and the value must be
221+
// retrievable through the matching getter on the returned ctx.
222+
tcs := []struct {
223+
name string
224+
set func(context.Context) context.Context
225+
get func(context.Context) string
226+
want string
227+
}{
228+
{"trace_id", func(c context.Context) context.Context { return WithTraceID(c, "t-1") }, TraceIDFromContext, "t-1"},
229+
{"tid", func(c context.Context) context.Context { return WithTID(c, "j-2") }, TIDFromContext, "j-2"},
230+
{"team_id", func(c context.Context) context.Context { return WithTeamID(c, "tm-3") }, TeamIDFromContext, "tm-3"},
231+
}
232+
for _, tc := range tcs {
233+
t.Run(tc.name, func(t *testing.T) {
234+
defer func() {
235+
if r := recover(); r != nil {
236+
t.Fatalf("setter panicked on nil ctx: %v", r)
237+
}
238+
}()
239+
//nolint:staticcheck // intentionally passing nil ctx to exercise the guard
240+
ctx := tc.set(nil)
241+
if ctx == nil {
242+
t.Fatal("setter returned nil ctx")
243+
}
244+
if got := tc.get(ctx); got != tc.want {
245+
t.Errorf("getter = %q, want %q", got, tc.want)
246+
}
247+
})
248+
}
249+
}

0 commit comments

Comments
 (0)