]> git.giacomolli.uk - onechat/blob - daemon_log_test.go
Log image and message sends
[onechat] / daemon_log_test.go
1 package main
2
3 import (
4 "errors"
5 "io"
6 "log/slog"
7 "strings"
8 "sync"
9 "testing"
10 "time"
11
12 "github.com/stretchr/testify/require"
13 )
14
15 func TestDaemonLogFormatsLevelsAndPrefixes(t *testing.T) {
16 testCases := []struct {
17 name string
18 level slog.Level
19 prefix string
20 priority string
21 }{
22 {
23 name: "error names the failed part",
24 level: slog.LevelError,
25 prefix: "onechat: ",
26 priority: "3",
27 },
28 {
29 name: "warning names the retried part",
30 level: slog.LevelWarn,
31 prefix: "telegram: ",
32 priority: "4",
33 },
34 {
35 name: "info names the connected part",
36 level: slog.LevelInfo,
37 prefix: "whatsapp: ",
38 priority: "6",
39 },
40 {
41 name: "debug has its own priority",
42 level: slog.LevelDebug,
43 prefix: "onechat: ",
44 priority: "7",
45 },
46 }
47
48 for _, tc := range testCases {
49 t.Run(tc.name, func(t *testing.T) {
50 for _, journal := range []bool{false, true} {
51 var out strings.Builder
52
53 handler := &DaemonLogHandler{
54 out: &out,
55 prefix: tc.prefix,
56 journal: journal,
57 mutex: &sync.Mutex{},
58 }
59
60 record := slog.NewRecord(time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC), tc.level, "ready", 0)
61 require.True(t, handler.Enabled(t.Context(), tc.level))
62 require.NoError(t, handler.Handle(t.Context(), record))
63
64 expected := "2026/01/02 03:04:05 " + tc.level.String() + " " + tc.prefix + "ready\n"
65 if journal {
66 expected = "<" + tc.priority + ">" + tc.prefix + "ready\n"
67 }
68
69 require.Equal(t, expected, out.String())
70 }
71 })
72 }
73 }
74
75 func TestDaemonLogKeepsAttributesInOrderAndCauseLast(t *testing.T) {
76 var out strings.Builder
77
78 handler := &DaemonLogHandler{
79 out: &out,
80 prefix: "telegram: ",
81 journal: true,
82 mutex: &sync.Mutex{},
83 }
84
85 logger := slog.New(handler).With("first", "two words", slog.Attr{})
86 logger.Warn("receive failed", "err", errors.New("502 Bad Gateway"), "retry", 2040*time.Millisecond, "count", 3, "short", 1500*time.Microsecond)
87 require.Equal(t, "<4>telegram: receive failed first=two words retry=2s count=3 short=2ms: 502 Bad Gateway\n", out.String())
88 }
89
90 func TestDaemonLogOmitsEmptyAttributesAndNilCause(t *testing.T) {
91 var out strings.Builder
92
93 handler := &DaemonLogHandler{
94 out: &out,
95 prefix: "onechat: ",
96 journal: true,
97 mutex: &sync.Mutex{},
98 }
99
100 slog.New(handler).Info("ready", slog.Attr{}, slog.Group("empty"), "err", nil)
101 require.Equal(t, "<6>onechat: ready\n", out.String())
102 }
103
104 func TestDaemonLogGroupsDoNotChangeOtherLoggers(t *testing.T) {
105 var out strings.Builder
106
107 handler := &DaemonLogHandler{
108 out: &out,
109 journal: true,
110 mutex: &sync.Mutex{},
111 }
112
113 logger := slog.New(handler)
114 grouped := logger.With("before", 1).WithGroup("outer").With("saved", 2).WithGroup("inner")
115 grouped.Info("done", slog.Group("", "count", 3))
116 logger.Info("ready")
117 require.Equal(t, "<6>done before=1 outer.saved=2 outer.inner.count=3\n<6>ready\n", out.String())
118 }
119
120 func TestDaemonLogReturnsWriteFailure(t *testing.T) {
121 handler := &DaemonLogHandler{
122 out: DaemonLogBrokenWriter{},
123 mutex: &sync.Mutex{},
124 }
125
126 err := handler.Handle(t.Context(), slog.NewRecord(time.Now(), slog.LevelInfo, "ready", 0))
127 require.ErrorIs(t, err, io.ErrClosedPipe)
128 require.ErrorContains(t, err, "write daemon log")
129 }
130
131 type DaemonLogBrokenWriter struct{}
132
133 func (w DaemonLogBrokenWriter) Write(_ []byte) (int, error) {
134 return 0, io.ErrClosedPipe
135 }