//go:build linux || freebsd package storage import ( "bufio" "encoding/json" "os" "path/filepath" "strings" "testing" ) func TestNewsletterStore_AppendCreatesFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "subs", "subscribers.jsonl") s := NewNewsletterStore(path) if err := s.Append(Subscriber{Email: "a@example.com", Form: "newsletter"}); err != nil { t.Fatalf("Append: %v", err) } if _, err := os.Stat(path); err != nil { t.Fatalf("expected file to exist, got %v", err) } } func TestNewsletterStore_AppendAndCount(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "subs.jsonl") s := NewNewsletterStore(path) for i, e := range []string{"a@x.com", "b@x.com", "c@x.com"} { if err := s.Append(Subscriber{Email: e, Form: "newsletter"}); err != nil { t.Fatalf("Append #%d: %v", i, err) } } n, err := s.Count() if err != nil { t.Fatalf("Count: %v", err) } if n != 3 { t.Errorf("expected 3 subscribers, got %d", n) } } func TestNewsletterStore_AppendIsOneLinePerRecord(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "subs.jsonl") s := NewNewsletterStore(path) for _, e := range []string{"a@x.com", "b@x.com"} { if err := s.Append(Subscriber{Email: e, Form: "n"}); err != nil { t.Fatalf("Append: %v", err) } } // Each line should be a self-contained JSON object. data, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile: %v", err) } scanner := bufio.NewScanner(strings.NewReader(string(data))) count := 0 for scanner.Scan() { line := scanner.Bytes() if len(line) == 0 { continue } var sub Subscriber if err := json.Unmarshal(line, &sub); err != nil { t.Fatalf("Unmarshal line: %v", err) } count++ } if err := scanner.Err(); err != nil { t.Fatalf("scanner.Err: %v", err) } if count != 2 { t.Errorf("expected 2 records, decoded %d", count) } } func TestNewsletterStore_CountOnMissingFileIsZero(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "does-not-exist.jsonl") s := NewNewsletterStore(path) n, err := s.Count() if err != nil { t.Fatalf("Count on missing file: %v", err) } if n != 0 { t.Errorf("expected 0 on missing file, got %d", n) } } func TestNewsletterStore_ListReturnsInsertionOrder(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "subs.jsonl") s := NewNewsletterStore(path) want := []string{"a@x.com", "b@x.com", "c@x.com"} for _, e := range want { _ = s.Append(Subscriber{Email: e, Form: "n"}) } subs, err := s.List() if err != nil { t.Fatalf("List: %v", err) } if len(subs) != len(want) { t.Fatalf("expected %d, got %d", len(want), len(subs)) } for i, sub := range subs { if sub.Email != want[i] { t.Errorf("position %d: want %q, got %q", i, want[i], sub.Email) } } } func TestNewsletterStore_SkipsMalformedLines(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "subs.jsonl") // Write a mix of valid and garbage lines. content := `{"email":"good1@x.com","form":"n","created_at":"2026-01-01T00:00:00Z"} this is not valid json {"email":"good2@x.com","form":"n","created_at":"2026-01-02T00:00:00Z"} {not even close to json ` if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } s := NewNewsletterStore(path) n, err := s.Count() if err != nil { t.Fatalf("Count: %v", err) } if n != 2 { t.Errorf("expected 2 valid records (skipping malformed), got %d", n) } } func TestNewsletterStore_PathReturnsConfiguredPath(t *testing.T) { path := filepath.Join(t.TempDir(), "subs.jsonl") s := NewNewsletterStore(path) if got := s.Path(); got != path { t.Errorf("Path: want %q, got %q", path, got) } }