1package bootstrap
2
3import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 corelogging "github.com/bnema/dumber/internal/logging"
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14)
15
16func TestRedactSensitiveContent(t *testing.T) {
17 t.Run("url_query_params", func(t *testing.T) {
18 line := "redirect to https://example.com/cb?code=abc123&token=secret#frag"
19 redacted := redactSensitiveContent(line)
20 assert.Contains(t, redacted, "https://example.com/cb")
21 assert.NotContains(t, redacted, "code=abc123")
22 assert.NotContains(t, redacted, "token=secret")
23 assert.NotContains(t, redacted, "#frag")
24 })
25
26 t.Run("json_secret_fields", func(t *testing.T) {
27 line := `{"token":"eyJhbGciOi","access_token":"sk-abc123","password":"hunter2"}`
28 redacted := redactSensitiveContent(line)
29 assert.NotContains(t, redacted, "eyJhbGciOi")
30 assert.NotContains(t, redacted, "sk-abc123")
31 assert.NotContains(t, redacted, "hunter2")
32 assert.Contains(t, redacted, `"token":"[REDACTED]"`)
33 assert.Contains(t, redacted, `"access_token":"[REDACTED]"`)
34 assert.Contains(t, redacted, `"password":"[REDACTED]"`)
35 })
36
37 t.Run("key_value_params", func(t *testing.T) {
38 line := "callback received password=secret123 token=abc456"
39 redacted := redactSensitiveContent(line)
40 assert.NotContains(t, redacted, "secret123")
41 assert.NotContains(t, redacted, "abc456")
42 assert.Contains(t, redacted, "password=[REDACTED]")
43 assert.Contains(t, redacted, "token=[REDACTED]")
44 })
45
46 t.Run("authorization_headers", func(t *testing.T) {
47 line := "Authorization: Bearer eyJhbGciOiJSUz"
48 redacted := redactSensitiveContent(line)
49 assert.NotContains(t, redacted, "eyJhbGciOiJSUz")
50 assert.Contains(t, redacted, "[REDACTED]")
51
52 line2 := "auth: basic dXNlcjpwYXNz"
53 redacted2 := redactSensitiveContent(line2)
54 assert.NotContains(t, redacted2, "dXNlcjpwYXNz")
55 assert.Contains(t, redacted2, "[REDACTED]")
56 })
57}
58
59func TestReadRedactedLogTail_RingBuffer(t *testing.T) {
60 dir := t.TempDir()
61 path := filepath.Join(dir, "test.log")
62
63 var lines []string
64 for i := range 10 {
65 lines = append(lines, fmt.Sprintf("line %d", i))
66 }
67 require.NoError(t, os.WriteFile(path, []byte(strings.Join(lines, "\n")+"\n"), filePerm))
68
69 t.Run("fewer_lines_than_requested", func(t *testing.T) {
70 result := readRedactedLogTail(path, 20)
71 assert.Len(t, result, 10)
72 assert.Equal(t, "line 0", result[0])
73 assert.Equal(t, "line 9", result[9])
74 })
75
76 t.Run("exact_tail", func(t *testing.T) {
77 result := readRedactedLogTail(path, 3)
78 assert.Len(t, result, 3)
79 assert.Equal(t, "line 7", result[0])
80 assert.Equal(t, "line 8", result[1])
81 assert.Equal(t, "line 9", result[2])
82 })
83}
84
85func TestWriteCrashReport(t *testing.T) {
86 logDir := t.TempDir()
87 sessionID := "crash-test-1"
88
89 logPath := filepath.Join(logDir, corelogging.SessionFilename(sessionID))
90 logBody := `{"level":"info","message":"opening https://example.com/path?a=1&b=2"}` +
91 "\n" +
92 `{"level":"warn","message":"callback code=abc token=def"}` +
93 "\n"
94 require.NoError(t, os.WriteFile(logPath, []byte(logBody), filePerm))
95
96 jsonPath, err := writeCrashReport(logDir, sessionID, 12345)
97 require.NoError(t, err)
98 require.NotEmpty(t, jsonPath)
99 assert.FileExists(t, jsonPath)
100
101 mdPath := strings.TrimSuffix(jsonPath, ".json") + ".md"
102 assert.FileExists(t, mdPath)
103
104 raw, err := os.ReadFile(jsonPath)
105 require.NoError(t, err)
106
107 var payload map[string]any
108 require.NoError(t, json.Unmarshal(raw, &payload))
109 assert.Equal(t, sessionID, payload["session_id"])
110 assert.InDelta(t, float64(12345), payload["crashed_pid"], 0)
111
112 tail, ok := payload["session_log_tail_redacted"].([]any)
113 require.True(t, ok, "session_log_tail_redacted should be a []any")
114 require.GreaterOrEqual(t, len(tail), 2, "expected at least 2 log tail entries")
115 first, ok := tail[0].(string)
116 require.True(t, ok)
117 last, ok := tail[len(tail)-1].(string)
118 require.True(t, ok)
119 text := first + last
120 assert.NotContains(t, text, "?a=1&b=2")
121 assert.NotContains(t, text, "code=abc")
122 assert.NotContains(t, text, "token=def")
123}