1package accesslog
2
3import (
4 "errors"
5 "testing"
6
7 "github.com/coreos/go-systemd/v22/journal"
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10)
11
12// capturedJournalCall records a single call to the stubbed journalSendFunc.
13type capturedJournalCall struct {
14 msg string
15 priority journal.Priority
16 vars map[string]string
17}
18
19// withJournalStub replaces journalEnabledFunc and journalSendFunc for the
20// duration of a test, restoring them afterwards.
21func withJournalStub(t *testing.T, enabled bool, captures *[]capturedJournalCall) {
22 t.Helper()
23 origEnabled := journalEnabledFunc
24 origSend := journalSendFunc
25
26 journalEnabledFunc = func() bool { return enabled }
27 journalSendFunc = func(msg string, priority journal.Priority, vars map[string]string) error {
28 *captures = append(*captures, capturedJournalCall{msg: msg, priority: priority, vars: vars})
29 return nil
30 }
31
32 t.Cleanup(func() {
33 journalEnabledFunc = origEnabled
34 journalSendFunc = origSend
35 })
36}
37
38func TestNewJournaldSink_JournaldUnavailable(t *testing.T) {
39 var calls []capturedJournalCall
40 withJournalStub(t, false, &calls)
41
42 _, err := New(Config{
43 Format: "json",
44 Output: "journald",
45 SyslogIdentifier: "gordon-access",
46 })
47 require.Error(t, err)
48 assert.Contains(t, err.Error(), "journald is not available")
49}
50
51func TestNewJournaldSink_EmptySyslogIdentifier(t *testing.T) {
52 var calls []capturedJournalCall
53 withJournalStub(t, true, &calls)
54
55 _, err := New(Config{
56 Format: "json",
57 Output: "journald",
58 SyslogIdentifier: "",
59 })
60 require.Error(t, err)
61 assert.Contains(t, err.Error(), "syslog_identifier")
62}
63
64func TestNewJournaldSink_WhitespaceSyslogIdentifier(t *testing.T) {
65 var calls []capturedJournalCall
66 withJournalStub(t, true, &calls)
67
68 _, err := New(Config{
69 Format: "json",
70 Output: "journald",
71 SyslogIdentifier: " ",
72 })
73 require.Error(t, err)
74 assert.Contains(t, err.Error(), "syslog_identifier")
75}
76
77func TestJournaldSink_Write_CallsJournalSend(t *testing.T) {
78 var calls []capturedJournalCall
79 withJournalStub(t, true, &calls)
80
81 writer, err := New(Config{
82 Format: "json",
83 Output: "journald",
84 SyslogIdentifier: "gordon-access",
85 })
86 require.NoError(t, err)
87
88 require.NoError(t, writer.Write(sampleEntry))
89 require.NoError(t, writer.Close())
90
91 require.Len(t, calls, 1)
92 assert.Equal(t, journal.PriInfo, calls[0].priority)
93 assert.Equal(t, "gordon-access", calls[0].vars["SYSLOG_IDENTIFIER"])
94 assert.NotEmpty(t, calls[0].msg, "message must be the serialized log line")
95}
96
97func TestJournaldSink_Write_SendsCustomIdentifier(t *testing.T) {
98 var calls []capturedJournalCall
99 withJournalStub(t, true, &calls)
100
101 writer, err := New(Config{
102 Format: "clf",
103 Output: "journald",
104 SyslogIdentifier: "my-app-access",
105 })
106 require.NoError(t, err)
107
108 require.NoError(t, writer.Write(sampleEntry))
109
110 require.Len(t, calls, 1)
111 assert.Equal(t, "my-app-access", calls[0].vars["SYSLOG_IDENTIFIER"])
112}
113
114func TestJournaldSink_Write_ReturnsErrorOnSendFailure(t *testing.T) {
115 origEnabled := journalEnabledFunc
116 origSend := journalSendFunc
117 t.Cleanup(func() {
118 journalEnabledFunc = origEnabled
119 journalSendFunc = origSend
120 })
121
122 journalEnabledFunc = func() bool { return true }
123 journalSendFunc = func(_ string, _ journal.Priority, _ map[string]string) error {
124 return errors.New("journal socket closed")
125 }
126
127 writer, err := New(Config{
128 Format: "json",
129 Output: "journald",
130 SyslogIdentifier: "gordon-access",
131 })
132 require.NoError(t, err)
133
134 err = writer.Write(sampleEntry)
135 require.Error(t, err)
136 assert.Contains(t, err.Error(), "journal socket closed")
137}