v0.29.0 21b3150

crash_report_test.go RawHistoryFolder

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 := 0; i < 10; i++ {
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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close