package store import ( "context" "database/sql" "path/filepath" "testing" ) func TestDraftsRoundTrip(t *testing.T) { s, ctx := testStore(t) id := seedMessage(t, s, ctx, "1", "question", "hello@where-woof.com") d, err := s.SaveDraft(ctx, id, "Thanks, got it.", "quick-ack") if err != nil { t.Fatalf("SaveDraft() error = %v", err) } if d.ID == 0 || d.Status != DraftStatusDraft || d.TemplateID != "quick-ack" { t.Fatalf("SaveDraft() = %+v", d) } if d.MessageID != id { t.Errorf("MessageID = %d, want %d", d.MessageID, id) } if err := s.UpdateDraftBody(ctx, d.ID, "Edited body."); err != nil { t.Fatalf("UpdateDraftBody() error = %v", err) } latest, ok, err := s.LatestDraft(ctx, id) if err != nil || !ok { t.Fatalf("LatestDraft() ok=%v err=%v", ok, err) } if latest.Body != "Edited body." || latest.Status != DraftStatusDraft { t.Errorf("latest = %+v", latest) } if err := s.MarkDraftSent(ctx, latest.ID); err != nil { t.Fatalf("MarkDraftSent() error = %v", err) } sent, _, err := s.LatestDraft(ctx, id) if err != nil { t.Fatalf("LatestDraft() after send error = %v", err) } if sent.Status != DraftStatusSent || sent.SentAt.IsZero() { t.Errorf("sent draft = %+v, want status=sent and a sent_at", sent) } } func TestLatestDraftMissing(t *testing.T) { s, ctx := testStore(t) id := seedMessage(t, s, ctx, "1", "x", "a@b.test") if _, ok, err := s.LatestDraft(ctx, id); err != nil || ok { t.Fatalf("LatestDraft() ok=%v err=%v, want ok=false", ok, err) } } func TestMessageByIDIncludesReplyFields(t *testing.T) { s, ctx := testStore(t) id, err := s.UpsertMessage(ctx, &Message{ GmailID: "99", FromAddr: "Customer ", Subject: "Booking", BodyText: "Saturday?", SourceAddress: "hello@where-woof.com", MessageIDHeader: "", }) if err != nil { t.Fatalf("UpsertMessage() error = %v", err) } msg, err := s.MessageByID(ctx, id) if err != nil { t.Fatalf("MessageByID() error = %v", err) } if msg.MessageIDHeader != "" { t.Errorf("MessageIDHeader = %q", msg.MessageIDHeader) } if msg.BodyText != "Saturday?" || msg.SourceAddress != "hello@where-woof.com" { t.Errorf("message = %+v", msg) } } func TestMigrationV5AddsReplyColumns(t *testing.T) { ctx := context.Background() path := filepath.Join(t.TempDir(), "old.db") // Simulate a v4 database: messages has no message_id_header and drafts has // no sent_at. Opening it must apply migration v5. db, err := sql.Open("sqlite", "file:"+path) if err != nil { t.Fatalf("open old db: %v", err) } stmts := []string{ `CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, gmail_id TEXT NOT NULL UNIQUE, thread_id TEXT, from_addr TEXT, to_addr TEXT, subject TEXT, body_text TEXT, source_address TEXT, date_sent TEXT, imap_uid INTEGER, imap_uidvalidity INTEGER, folder TEXT NOT NULL DEFAULT 'inbox', mailbox TEXT NOT NULL DEFAULT 'INBOX', received_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime('now')))`, `CREATE TABLE drafts ( id INTEGER PRIMARY KEY AUTOINCREMENT, message_id INTEGER NOT NULL, body TEXT NOT NULL, template_id TEXT, status TEXT NOT NULL DEFAULT 'draft', created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')))`, `CREATE TABLE schema_migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (datetime('now')))`, `INSERT INTO schema_migrations (version) VALUES (4)`, } for _, q := range stmts { if _, err := db.ExecContext(ctx, q); err != nil { t.Fatalf("seed old db: %v", err) } } _ = db.Close() s, err := Open(ctx, path) if err != nil { t.Fatalf("Open() on v4 database error = %v", err) } defer s.Close() v, err := s.SchemaVersion(ctx) if err != nil { t.Fatalf("SchemaVersion() error = %v", err) } if v < 5 { t.Errorf("SchemaVersion() = %d, want >= 5", v) } for _, tc := range []struct{ table, col string }{ {"messages", "message_id_header"}, {"drafts", "sent_at"}, } { var n int q := `SELECT COUNT(*) FROM pragma_table_info('` + tc.table + `') WHERE name = ?` if err := s.DB().QueryRowContext(ctx, q, tc.col).Scan(&n); err != nil { t.Fatalf("count %s.%s: %v", tc.table, tc.col, err) } if n != 1 { t.Errorf("%s.%s missing after migration v5", tc.table, tc.col) } } }