]> git.giacomolli.uk - onechat/blob - log_test.go
Add the compaction view
[onechat] / log_test.go
1 package main
2
3 import (
4         "os"
5         "path/filepath"
6         "testing"
7         "time"
8
9         "github.com/stretchr/testify/require"
10 )
11
12 func TestLogReopenLoadsTheSameMessages(t *testing.T) {
13         root := t.TempDir()
14         clock := newTestClock()
15         log, err := openLog(t.Context(), root, clock.Now)
16         require.NoError(t, err)
17
18         var written []Message
19         for _, kind := range []string{"user", "reply", "tool", "echo", "work", "note"} {
20                 message, err := log.Append(kind, kind+" text ✓")
21                 require.NoError(t, err)
22
23                 written = append(written, message)
24         }
25
26         require.Equal(t, 5, written[5].ID)
27         require.Equal(t, len("note text ✓"), written[5].Size)
28         require.NoError(t, log.Close())
29
30         log, err = openLog(t.Context(), root, clock.Now)
31         require.NoError(t, err)
32
33         defer func() {
34                 require.NoError(t, log.Close())
35         }()
36
37         require.Equal(t, written, log.messages)
38
39         message, err := log.Append("user", "after the reopen")
40         require.NoError(t, err)
41         require.Equal(t, 6, message.ID)
42 }
43
44 func TestLogWritesOneFilePerDay(t *testing.T) {
45         root := t.TempDir()
46         clock := newTestClock()
47         log, err := openLog(t.Context(), root, clock.Now)
48         require.NoError(t, err)
49
50         defer func() {
51                 require.NoError(t, log.Close())
52         }()
53
54         _, err = log.Append("user", "first day")
55         require.NoError(t, err)
56
57         clock.now = clock.now.Add(24 * time.Hour)
58         _, err = log.Append("user", "second day")
59         require.NoError(t, err)
60
61         first, err := os.ReadFile(filepath.Join(root, "main", "2026-01-01.jsonl"))
62         require.NoError(t, err)
63         require.Equal(t, `{"i":0,"kind":"user","text":"first day","size":9,"date":"2026-01-01T12:00:00Z"}`+"\n", string(first))
64
65         second, err := os.ReadFile(filepath.Join(root, "main", "2026-01-02.jsonl"))
66         require.NoError(t, err)
67         require.Equal(t, `{"i":1,"kind":"user","text":"second day","size":10,"date":"2026-01-02T12:00:00Z"}`+"\n", string(second))
68 }
69
70 func TestLogAllowsOneWriter(t *testing.T) {
71         root := t.TempDir()
72         clock := newTestClock()
73         log, err := openLog(t.Context(), root, clock.Now)
74         require.NoError(t, err)
75
76         _, err = openLog(t.Context(), root, clock.Now)
77         require.ErrorIs(t, err, errLocked)
78         require.NoError(t, log.Close())
79
80         log, err = openLog(t.Context(), root, clock.Now)
81         require.NoError(t, err)
82         require.NoError(t, log.Close())
83 }
84
85 func TestLogRejectsAnUnknownKind(t *testing.T) {
86         log, err := openLog(t.Context(), t.TempDir(), newTestClock().Now)
87         require.NoError(t, err)
88
89         defer func() {
90                 require.NoError(t, log.Close())
91         }()
92
93         _, err = log.Append("talk", "text")
94         require.ErrorContains(t, err, `unknown message kind "talk"`)
95         require.Equal(t, 0, log.Len())
96 }
97
98 func TestLogRejectsAGapInTheIDs(t *testing.T) {
99         root := t.TempDir()
100         require.NoError(t, os.MkdirAll(filepath.Join(root, "main"), 0o700))
101
102         line := `{"i":1,"kind":"user","text":"x","size":1,"date":"2026-01-01T12:00:00Z"}` + "\n"
103         require.NoError(t, os.WriteFile(filepath.Join(root, "main", "2026-01-01.jsonl"), []byte(line), 0o600))
104
105         _, err := openLog(t.Context(), root, newTestClock().Now)
106         require.ErrorContains(t, err, "expected message 0, found 1")
107 }
108
109 // testClock is a clock that tests set by hand.
110 type testClock struct {
111         now time.Time
112 }
113
114 func newTestClock() *testClock {
115         return &testClock{
116                 now: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC),
117         }
118 }
119
120 func (c *testClock) Now() time.Time {
121         return c.now
122 }