test: add comprehensive Comments API tests (bd-d68f)

- Test AddIssueComment basic functionality
- Test GetIssueComments retrieval and ordering
- Test edge cases (empty, nonexistent issues)
- Test dirty flag marking
- Test comment isolation across issues

Improves sqlite package coverage: 69.1% → 70.6%
This commit is contained in:
Steve Yegge
2025-11-01 22:53:38 -07:00
parent d86f359e63
commit 1849ffe17a
2 changed files with 371 additions and 3 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,368 @@
package sqlite
import (
"context"
"testing"
"github.com/steveyegge/beads/internal/types"
)
// TestAddIssueComment tests basic comment addition
func TestAddIssueComment(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create an issue
issue := &types.Issue{
Title: "Test issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
// Add a comment
comment, err := store.AddIssueComment(ctx, issue.ID, "alice", "This is a test comment")
if err != nil {
t.Fatalf("AddIssueComment failed: %v", err)
}
// Verify comment fields
if comment.IssueID != issue.ID {
t.Errorf("Expected IssueID %s, got %s", issue.ID, comment.IssueID)
}
if comment.Author != "alice" {
t.Errorf("Expected Author 'alice', got '%s'", comment.Author)
}
if comment.Text != "This is a test comment" {
t.Errorf("Expected Text 'This is a test comment', got '%s'", comment.Text)
}
if comment.ID == 0 {
t.Error("Expected non-zero comment ID")
}
if comment.CreatedAt.IsZero() {
t.Error("Expected non-zero CreatedAt timestamp")
}
}
// TestAddIssueCommentNonexistentIssue tests adding comment to non-existent issue
func TestAddIssueCommentNonexistentIssue(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Try to add comment to non-existent issue
_, err := store.AddIssueComment(ctx, "nonexistent-id", "alice", "comment")
if err == nil {
t.Fatal("Expected error when adding comment to non-existent issue, got nil")
}
}
// TestGetIssueComments tests retrieving comments
func TestGetIssueComments(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create an issue
issue := &types.Issue{
Title: "Test issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
// Add multiple comments
testComments := []struct {
author string
text string
}{
{"alice", "First comment"},
{"bob", "Second comment"},
{"charlie", "Third comment"},
}
for _, tc := range testComments {
_, err := store.AddIssueComment(ctx, issue.ID, tc.author, tc.text)
if err != nil {
t.Fatalf("AddIssueComment failed: %v", err)
}
}
// Retrieve comments
comments, err := store.GetIssueComments(ctx, issue.ID)
if err != nil {
t.Fatalf("GetIssueComments failed: %v", err)
}
// Verify number of comments
if len(comments) != len(testComments) {
t.Fatalf("Expected %d comments, got %d", len(testComments), len(comments))
}
// Verify comment content and ordering (should be chronological)
for i, comment := range comments {
if comment.Author != testComments[i].author {
t.Errorf("Comment %d: expected author %s, got %s", i, testComments[i].author, comment.Author)
}
if comment.Text != testComments[i].text {
t.Errorf("Comment %d: expected text %s, got %s", i, testComments[i].text, comment.Text)
}
if comment.IssueID != issue.ID {
t.Errorf("Comment %d: expected IssueID %s, got %s", i, issue.ID, comment.IssueID)
}
}
}
// TestGetIssueCommentsOrdering tests that comments are returned in chronological order
func TestGetIssueCommentsOrdering(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create an issue
issue := &types.Issue{
Title: "Test issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
// Add comments with identifiable ordering
for i := 1; i <= 5; i++ {
text := "Comment " + string(rune('0'+i))
_, err := store.AddIssueComment(ctx, issue.ID, "alice", text)
if err != nil {
t.Fatalf("AddIssueComment failed: %v", err)
}
}
// Retrieve comments
comments, err := store.GetIssueComments(ctx, issue.ID)
if err != nil {
t.Fatalf("GetIssueComments failed: %v", err)
}
// Verify chronological ordering
if len(comments) != 5 {
t.Fatalf("Expected 5 comments, got %d", len(comments))
}
for i := 0; i < len(comments); i++ {
expectedText := "Comment " + string(rune('0'+i+1))
if comments[i].Text != expectedText {
t.Errorf("Comment %d: expected text %s, got %s", i, expectedText, comments[i].Text)
}
// Verify timestamps are in ascending order
if i > 0 && comments[i].CreatedAt.Before(comments[i-1].CreatedAt) {
t.Errorf("Comments not in chronological order: comment %d (%v) is before comment %d (%v)",
i, comments[i].CreatedAt, i-1, comments[i-1].CreatedAt)
}
}
}
// TestGetIssueCommentsEmpty tests retrieving comments for issue with no comments
func TestGetIssueCommentsEmpty(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create an issue
issue := &types.Issue{
Title: "Test issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
// Retrieve comments (should be empty)
comments, err := store.GetIssueComments(ctx, issue.ID)
if err != nil {
t.Fatalf("GetIssueComments failed: %v", err)
}
if len(comments) != 0 {
t.Errorf("Expected 0 comments, got %d", len(comments))
}
}
// TestGetIssueCommentsNonexistentIssue tests retrieving comments for non-existent issue
func TestGetIssueCommentsNonexistentIssue(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Retrieve comments for non-existent issue
comments, err := store.GetIssueComments(ctx, "nonexistent-id")
if err != nil {
t.Fatalf("GetIssueComments failed: %v", err)
}
// Should return empty slice, not error
if len(comments) != 0 {
t.Errorf("Expected 0 comments for non-existent issue, got %d", len(comments))
}
}
// TestAddIssueCommentEmptyText tests adding comment with empty text
func TestAddIssueCommentEmptyText(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create an issue
issue := &types.Issue{
Title: "Test issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
// Add comment with empty text (should succeed - validation is caller's responsibility)
comment, err := store.AddIssueComment(ctx, issue.ID, "alice", "")
if err != nil {
t.Fatalf("AddIssueComment with empty text failed: %v", err)
}
if comment.Text != "" {
t.Errorf("Expected empty text, got '%s'", comment.Text)
}
}
// TestAddIssueCommentMarksDirty tests that adding a comment marks the issue dirty
func TestAddIssueCommentMarksDirty(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create an issue
issue := &types.Issue{
Title: "Test issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
// Clear dirty flag (simulating after export)
if err := store.ClearDirtyIssuesByID(ctx, []string{issue.ID}); err != nil {
t.Fatalf("ClearDirtyIssuesByID failed: %v", err)
}
// Add a comment
_, err := store.AddIssueComment(ctx, issue.ID, "alice", "test comment")
if err != nil {
t.Fatalf("AddIssueComment failed: %v", err)
}
// Verify issue is marked dirty
var exists bool
err = store.db.QueryRowContext(ctx, `SELECT EXISTS(SELECT 1 FROM dirty_issues WHERE issue_id = ?)`, issue.ID).Scan(&exists)
if err != nil {
t.Fatalf("Failed to check dirty flag: %v", err)
}
if !exists {
t.Error("Expected issue to be marked dirty after adding comment")
}
}
// TestGetIssueCommentsMultipleIssues tests that comments are properly isolated per issue
func TestGetIssueCommentsMultipleIssues(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
// Create two issues
issue1 := &types.Issue{
Title: "Issue 1",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
issue2 := &types.Issue{
Title: "Issue 2",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, issue1, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
if err := store.CreateIssue(ctx, issue2, "test-user"); err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
// Add comments to each issue
_, err := store.AddIssueComment(ctx, issue1.ID, "alice", "Comment for issue 1")
if err != nil {
t.Fatalf("AddIssueComment failed: %v", err)
}
_, err = store.AddIssueComment(ctx, issue1.ID, "bob", "Another comment for issue 1")
if err != nil {
t.Fatalf("AddIssueComment failed: %v", err)
}
_, err = store.AddIssueComment(ctx, issue2.ID, "charlie", "Comment for issue 2")
if err != nil {
t.Fatalf("AddIssueComment failed: %v", err)
}
// Retrieve comments for issue 1
comments1, err := store.GetIssueComments(ctx, issue1.ID)
if err != nil {
t.Fatalf("GetIssueComments failed: %v", err)
}
// Retrieve comments for issue 2
comments2, err := store.GetIssueComments(ctx, issue2.ID)
if err != nil {
t.Fatalf("GetIssueComments failed: %v", err)
}
// Verify each issue has the correct number of comments
if len(comments1) != 2 {
t.Errorf("Expected 2 comments for issue 1, got %d", len(comments1))
}
if len(comments2) != 1 {
t.Errorf("Expected 1 comment for issue 2, got %d", len(comments2))
}
// Verify comments belong to correct issues
for _, c := range comments1 {
if c.IssueID != issue1.ID {
t.Errorf("Comment has wrong IssueID: expected %s, got %s", issue1.ID, c.IssueID)
}
}
for _, c := range comments2 {
if c.IssueID != issue2.ID {
t.Errorf("Comment has wrong IssueID: expected %s, got %s", issue2.ID, c.IssueID)
}
}
}