Files
dingtalk-workspace-cli/internal/logging/coverage_edges_test.go

174 lines
6.0 KiB
Go

package logging
import (
"bytes"
"context"
"errors"
"io"
"log/slog"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/config"
)
func TestCrossPlatformCoverageLoggerFailureAndRecoveryEdges(t *testing.T) {
if (*FileLogger)(nil).Writer() != io.Discard || (&FileLogger{}).Writer() != io.Discard {
t.Fatal("uninitialized Writer should discard")
}
dir := t.TempDir()
logPath := filepath.Join(dir, "direct.log")
file, err := NewRotatingFile(logPath)
if err != nil {
t.Fatalf("NewRotatingFile: %v", err)
}
if _, err := file.Write([]byte("entry")); err != nil {
t.Fatalf("write rotating file: %v", err)
}
if err := file.Close(); err != nil {
t.Fatalf("close rotating file: %v", err)
}
configured := Setup(t.TempDir())
if configured.Writer() == io.Discard {
t.Fatal("configured Writer should expose rotating writer")
}
_ = configured.Close()
if _, err := NewRotatingFile(filepath.Join(dir, "missing", "file")); err == nil {
t.Fatal("rotating file in missing directory should fail")
}
blocked := filepath.Join(t.TempDir(), logSubDir, logFileName)
if err := os.MkdirAll(blocked, config.DirPerm); err != nil {
t.Fatalf("create blocking directory: %v", err)
}
nop := Setup(filepath.Dir(filepath.Dir(blocked)))
if nop.writer != nil {
t.Fatal("Setup with directory at log path should return no-op logger")
}
oldOpen := openLogFile
t.Cleanup(func() { openLogFile = oldOpen })
wantErr := errors.New("open failed")
openLogFile = func(string, int, os.FileMode) (*os.File, error) { return nil, wantErr }
w := newRotatingWriter(logPath, 1, 1)
if _, err := w.Write([]byte("x")); !errors.Is(err, wantErr) {
t.Fatalf("reopen error = %v", err)
}
closedFile := func() *os.File {
f, err := os.CreateTemp(t.TempDir(), "closed")
if err != nil {
t.Fatalf("create temp file: %v", err)
}
_ = f.Close()
return f
}
openLogFile = func(string, int, os.FileMode) (*os.File, error) { return closedFile(), nil }
if err := newRotatingWriter(logPath, 1, 1).open(); err == nil {
t.Fatal("open should report stat failure")
}
w = newRotatingWriter(logPath, 1, 1)
if err := w.reopenLocked(); err == nil {
t.Fatal("reopen should report stat failure")
}
openLogFile = oldOpen
w = newRotatingWriter(logPath, 1, 1)
if err := w.open(); err != nil {
t.Fatalf("open before rotation failure: %v", err)
}
openLogFile = func(string, int, os.FileMode) (*os.File, error) { return nil, wantErr }
if _, err := w.Write([]byte("oversized")); !errors.Is(err, os.ErrClosed) {
t.Fatalf("rotation reopen error = %v", err)
}
openLogFile = oldOpen
w = newRotatingWriter(filepath.Join(t.TempDir(), "reopened.log"), 100, 1)
if _, err := w.Write([]byte("reopened")); err != nil {
t.Fatalf("successful reopen write: %v", err)
}
_ = w.close()
w = newRotatingWriter(filepath.Join(t.TempDir(), "closed-write.log"), 100, 1)
if err := w.open(); err != nil {
t.Fatalf("open for closed write: %v", err)
}
_ = w.file.Close()
if _, err := w.Write([]byte("x")); err == nil {
t.Fatal("write to externally closed file should fail")
}
if err := w.close(); err == nil {
t.Fatal("close of externally closed file should fail")
}
if err := w.close(); err != nil {
t.Fatalf("second close = %v", err)
}
}
func TestCrossPlatformCoverageLoggingTransportOptionalAndNilBranches(t *testing.T) {
LogRequest(nil, "", "", "", 0)
LogRequestBody(nil, "tools/call", "", "", nil)
LogResponse(nil, "", "", "", 0, 0, 0, nil)
LogResponseBody(nil, "", "", 0, nil, "")
LogRetryAttempt(nil, "", "", 0, 0, 0, 0, nil)
LogErrorClassified(nil, "", "", "", "", 0, 0, false, "")
LogCommandStart(nil, "", "", "", "", "", false, 0)
LogCommandEnd(nil, "", "", "", true, 0, "", "")
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
LogResponseBody(logger, "method", "exec", 200, []byte("ok"), "")
LogRetryAttempt(logger, "method", "exec", 1, 2, 503, time.Second, nil)
LogErrorClassified(logger, "method", "exec", "category", "reason", 0, 0, false, "")
LogErrorClassified(logger, "method", "exec", "category", "reason", 418, -32000, false, "trace")
LogCommandStart(logger, "exec", "product", "tool", "https://example.test?q=secret", "v1", true, 0)
LogCommandStart(logger, "exec", "product", "tool", "https://example.test", "v1", true, 30)
LogCommandEnd(logger, "exec", "product", "tool", true, time.Second, "", "")
LogCommandEnd(logger, "exec", "product", "tool", false, time.Second, "category", "reason")
if !strings.Contains(buf.String(), "command_start") || !strings.Contains(buf.String(), "command_end") {
t.Fatalf("optional log records = %q", buf.String())
}
}
func TestCrossPlatformCoverageRedactionAndMultiHandlerEdges(t *testing.T) {
if got := TruncateBody([]byte{0xff, 0xfe, 'a'}, 2); !strings.Contains(got, "truncated") {
t.Fatalf("invalid UTF-8 truncation = %q", got)
}
if got := SanitizeArguments(map[string]any{"unsupported": make(chan int)}, 100); got != "{}" {
t.Fatalf("marshal failure sanitization = %q", got)
}
if RedactHeaders(http.Header{}) != nil {
t.Fatal("empty headers should return nil")
}
wantErr := errors.New("handler failure")
h := &failingLogHandler{err: wantErr, enabled: true}
multi := NewMultiHandler(&failingLogHandler{}, h)
if err := multi.Handle(context.Background(), slog.NewRecord(time.Now(), slog.LevelInfo, "message", 0)); !errors.Is(err, wantErr) {
t.Fatalf("multi handler error = %v", err)
}
if !multi.Enabled(context.Background(), slog.LevelInfo) {
t.Fatal("multi handler should be enabled")
}
if NewMultiHandler(&failingLogHandler{}).Enabled(context.Background(), slog.LevelInfo) {
t.Fatal("disabled multi handler should not be enabled")
}
}
type failingLogHandler struct {
err error
enabled bool
}
func (h *failingLogHandler) Enabled(context.Context, slog.Level) bool { return h.enabled }
func (h *failingLogHandler) Handle(context.Context, slog.Record) error {
return h.err
}
func (h *failingLogHandler) WithAttrs([]slog.Attr) slog.Handler { return h }
func (h *failingLogHandler) WithGroup(string) slog.Handler { return h }