// SPDX-License-Identifier: GPL-3.0-or-later package eventlog import ( "bufio" "encoding/json" "os" "path/filepath" "sync" "testing" ) func TestWriterAppendsJSONLWithPrivatePermissions(t *testing.T) { path := filepath.Join(t.TempDir(), "state", "events.jsonl") writer, err := New(path, 1<<20) if err != nil { t.Fatal(err) } for _, record := range []map[string]any{ {"event_type": "status", "sequence": 1}, {"event_type": "alert", "sequence": 2}, } { if err := writer.Append(record); err != nil { t.Fatal(err) } } rows := readRows(t, path) if len(rows) != 2 || rows[0]["sequence"] != float64(1) || rows[1]["sequence"] != float64(2) { t.Fatalf("rows=%#v", rows) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if got := info.Mode().Perm(); got != 0o600 { t.Fatalf("mode=%#o", got) } } func TestWriterRotatesBeforeCrossingBound(t *testing.T) { directory := t.TempDir() path := filepath.Join(directory, "events.jsonl") first := map[string]any{"event_type": "status", "marker": "first"} encoded, err := json.Marshal(first) if err != nil { t.Fatal(err) } writer, err := New(path, int64(len(encoded)+1)) if err != nil { t.Fatal(err) } if err := writer.Append(first); err != nil { t.Fatal(err) } if err := writer.Append(map[string]any{"event_type": "alert", "marker": "second"}); err != nil { t.Fatal(err) } current := readRows(t, path) rotated := readRows(t, path+".1") if len(current) != 1 || current[0]["marker"] != "second" { t.Fatalf("current=%#v", current) } if len(rotated) != 1 || rotated[0]["marker"] != "first" { t.Fatalf("rotated=%#v", rotated) } } func TestWriterSerializesConcurrentAppends(t *testing.T) { path := filepath.Join(t.TempDir(), "events.jsonl") writer, err := New(path, 1<<20) if err != nil { t.Fatal(err) } var wait sync.WaitGroup for index := range 32 { wait.Add(1) go func() { defer wait.Done() if err := writer.Append(map[string]any{"event_type": "status", "sequence": index}); err != nil { t.Errorf("append: %v", err) } }() } wait.Wait() if rows := readRows(t, path); len(rows) != 32 { t.Fatalf("row count=%d", len(rows)) } } func TestNewRejectsInvalidConfiguration(t *testing.T) { if _, err := New("", 100); err == nil { t.Fatal("empty path accepted") } if _, err := New(filepath.Join(t.TempDir(), "events.jsonl"), 0); err == nil { t.Fatal("zero bound accepted") } } func TestWriterRejectsRecordLargerThanReaderLimit(t *testing.T) { path := filepath.Join(t.TempDir(), "events.jsonl") writer, err := New(path, 2<<20) if err != nil { t.Fatal(err) } if err := writer.Append(map[string]any{"event_type": "alert", "payload": make([]byte, maxRecordBytes)}); err == nil { t.Fatal("oversized record accepted") } } func readRows(t *testing.T, path string) []map[string]any { t.Helper() file, err := os.Open(path) if err != nil { t.Fatal(err) } defer file.Close() var rows []map[string]any scanner := bufio.NewScanner(file) for scanner.Scan() { var row map[string]any if err := json.Unmarshal(scanner.Bytes(), &row); err != nil { t.Fatal(err) } rows = append(rows, row) } if err := scanner.Err(); err != nil { t.Fatal(err) } return rows }