X-Git-Url: http://git.giacomolli.uk/onechat/blobdiff_plain/feda522eba3e2a27c91741c77d0d01f1840c0004..f2e67d5579593e2d4f1cb2d9165b851c52a06715:/log_test.go?ds=sidebyside diff --git a/log_test.go b/log_test.go index 60b9b64..306b21f 100644 --- a/log_test.go +++ b/log_test.go @@ -62,7 +62,7 @@ func TestLogBuildsAMissingIndexAgain(t *testing.T) { require.NoError(t, log.Close()) // A missing index, and an index that a crash cut in a record. - for _, cut := range []func(string) error{os.Remove, truncateToOneAndAHalf} { + for _, cut := range []func(string) error{os.Remove, logTruncateToOneAndAHalf} { require.NoError(t, cut(filepath.Join(root, "index", "main.idx"))) log, err = openLog(t.Context(), root, clock.Now) @@ -141,22 +141,59 @@ func TestLogRejectsAGapInTheIDs(t *testing.T) { require.ErrorContains(t, err, "expected message 0, found 1") } -// truncateToOneAndAHalf cuts the index at path in its second record. -func truncateToOneAndAHalf(path string) error { - return os.Truncate(path, positionBytes+positionBytes/2) -} +func TestLogCutsAnIncompleteLastLine(t *testing.T) { + root := t.TempDir() + clock := newTestClock() + log, err := openLog(t.Context(), root, clock.Now) + require.NoError(t, err) + + _, err = log.Append("user", "whole") + require.NoError(t, err) + require.NoError(t, log.Close()) + + path := filepath.Join(root, "main", "2026-01-01.jsonl") + file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0) + require.NoError(t, err) + + _, err = file.WriteString(`{"i":1,"kind":"user","te`) + require.NoError(t, err) + require.NoError(t, file.Close()) + + log, err = openLog(t.Context(), root, clock.Now) + require.NoError(t, err) + require.Equal(t, 1, log.Len()) + + message, err := log.Append("user", "after the cut") + require.NoError(t, err) + require.Equal(t, 1, message.ID) + require.NoError(t, log.Close()) + + log, err = openLog(t.Context(), root, clock.Now) + require.NoError(t, err) + + defer func() { + require.NoError(t, log.Close()) + }() -// testClock is a clock that tests set by hand. -type testClock struct { - now time.Time + message, err = log.Message(1) + require.NoError(t, err) + require.Equal(t, "after the cut", message.Text) } -func newTestClock() *testClock { - return &testClock{ - now: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC), - } +func TestLogRejectsAnIncompleteLineBeforeTheLastDay(t *testing.T) { + root := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(root, "main"), 0o700)) + + first := `{"i":0,"kind":"user","text":"x","size":1,"date":"2026-01-01T12:00:00Z"}` + "\n" + `{"i":1,"kin` + second := `{"i":1,"kind":"user","text":"y","size":1,"date":"2026-01-02T12:00:00Z"}` + "\n" + require.NoError(t, os.WriteFile(filepath.Join(root, "main", "2026-01-01.jsonl"), []byte(first), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(root, "main", "2026-01-02.jsonl"), []byte(second), 0o600)) + + _, err := openLog(t.Context(), root, newTestClock().Now) + require.ErrorContains(t, err, "ends in an incomplete line") } -func (c *testClock) Now() time.Time { - return c.now +// logTruncateToOneAndAHalf cuts the index at path in its second record. +func logTruncateToOneAndAHalf(path string) error { + return os.Truncate(path, positionBytes+positionBytes/2) }