Refactor high-complexity test functions (gocyclo)

Extracted helper structs for 5 complex test functions, reducing cyclomatic complexity from 31-35 to <10:

- TestLibraryIntegration: integrationTestHelper with create/assert methods
- TestExportImport: exportImportHelper with JSONL encoding/validation
- TestListCommand: listTestHelper with search and assertions
- TestGetEpicsEligibleForClosure: epicTestHelper with epic-specific queries
- TestCreateIssues: createIssuesTestHelper with batch creation helpers

All tests pass. Closes bd-55.

Amp-Thread-ID: https://ampcode.com/threads/T-39807355-8790-4646-a98d-d40472e1bd2c
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-25 13:20:16 -07:00
parent f6e37bd25d
commit 94fb9fa531
6 changed files with 681 additions and 940 deletions

View File

@@ -7,208 +7,150 @@ import (
"github.com/steveyegge/beads/internal/types"
)
func TestGetEpicsEligibleForClosure(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
// epicTestHelper provides test setup and assertion methods
type epicTestHelper struct {
t *testing.T
ctx context.Context
store *SQLiteStorage
}
ctx := context.Background()
func newEpicTestHelper(t *testing.T, store *SQLiteStorage) *epicTestHelper {
return &epicTestHelper{t: t, ctx: context.Background(), store: store}
}
// Create an epic
func (h *epicTestHelper) createEpic(title string) *types.Issue {
epic := &types.Issue{
Title: "Test Epic",
Title: title,
Description: "Epic for testing",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeEpic,
}
err := store.CreateIssue(ctx, epic, "test-user")
if err != nil {
t.Fatalf("CreateIssue (epic) failed: %v", err)
if err := h.store.CreateIssue(h.ctx, epic, "test-user"); err != nil {
h.t.Fatalf("CreateIssue (epic) failed: %v", err)
}
return epic
}
// Create two child tasks
task1 := &types.Issue{
Title: "Task 1",
func (h *epicTestHelper) createTask(title string) *types.Issue {
task := &types.Issue{
Title: title,
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err = store.CreateIssue(ctx, task1, "test-user")
if err != nil {
t.Fatalf("CreateIssue (task1) failed: %v", err)
if err := h.store.CreateIssue(h.ctx, task, "test-user"); err != nil {
h.t.Fatalf("CreateIssue (%s) failed: %v", title, err)
}
return task
}
task2 := &types.Issue{
Title: "Task 2",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeTask,
}
err = store.CreateIssue(ctx, task2, "test-user")
if err != nil {
t.Fatalf("CreateIssue (task2) failed: %v", err)
}
// Add parent-child dependencies
dep1 := &types.Dependency{
IssueID: task1.ID,
DependsOnID: epic.ID,
func (h *epicTestHelper) addParentChildDependency(childID, parentID string) {
dep := &types.Dependency{
IssueID: childID,
DependsOnID: parentID,
Type: types.DepParentChild,
}
err = store.AddDependency(ctx, dep1, "test-user")
if err != nil {
t.Fatalf("AddDependency (task1) failed: %v", err)
if err := h.store.AddDependency(h.ctx, dep, "test-user"); err != nil {
h.t.Fatalf("AddDependency failed: %v", err)
}
}
dep2 := &types.Dependency{
IssueID: task2.ID,
DependsOnID: epic.ID,
Type: types.DepParentChild,
}
err = store.AddDependency(ctx, dep2, "test-user")
if err != nil {
t.Fatalf("AddDependency (task2) failed: %v", err)
func (h *epicTestHelper) closeIssue(id, reason string) {
if err := h.store.CloseIssue(h.ctx, id, reason, "test-user"); err != nil {
h.t.Fatalf("CloseIssue (%s) failed: %v", id, err)
}
}
// Test 1: Epic with open children should NOT be eligible for closure
epics, err := store.GetEpicsEligibleForClosure(ctx)
func (h *epicTestHelper) getEligibleEpics() []*types.EpicStatus {
epics, err := h.store.GetEpicsEligibleForClosure(h.ctx)
if err != nil {
t.Fatalf("GetEpicsEligibleForClosure failed: %v", err)
h.t.Fatalf("GetEpicsEligibleForClosure failed: %v", err)
}
return epics
}
func (h *epicTestHelper) findEpic(epics []*types.EpicStatus, epicID string) (*types.EpicStatus, bool) {
for _, e := range epics {
if e.Epic.ID == epicID {
return e, true
}
}
return nil, false
}
func (h *epicTestHelper) assertEpicStats(epic *types.EpicStatus, totalChildren, closedChildren int, eligible bool, desc string) {
if epic.TotalChildren != totalChildren {
h.t.Errorf("%s: Expected %d total children, got %d", desc, totalChildren, epic.TotalChildren)
}
if epic.ClosedChildren != closedChildren {
h.t.Errorf("%s: Expected %d closed children, got %d", desc, closedChildren, epic.ClosedChildren)
}
if epic.EligibleForClose != eligible {
h.t.Errorf("%s: Expected eligible=%v, got %v", desc, eligible, epic.EligibleForClose)
}
}
func (h *epicTestHelper) assertEpicNotFound(epics []*types.EpicStatus, epicID string, desc string) {
if _, found := h.findEpic(epics, epicID); found {
h.t.Errorf("%s: Epic %s should not be in results", desc, epicID)
}
}
func (h *epicTestHelper) assertEpicFound(epics []*types.EpicStatus, epicID string, desc string) *types.EpicStatus {
epic, found := h.findEpic(epics, epicID)
if !found {
h.t.Fatalf("%s: Epic %s not found in results", desc, epicID)
}
return epic
}
func TestGetEpicsEligibleForClosure(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
h := newEpicTestHelper(t, store)
epic := h.createEpic("Test Epic")
task1 := h.createTask("Task 1")
task2 := h.createTask("Task 2")
h.addParentChildDependency(task1.ID, epic.ID)
h.addParentChildDependency(task2.ID, epic.ID)
// Test 1: Epic with open children should NOT be eligible
epics := h.getEligibleEpics()
if len(epics) == 0 {
t.Fatal("Expected at least one epic")
}
found := false
for _, e := range epics {
if e.Epic.ID == epic.ID {
found = true
if e.TotalChildren != 2 {
t.Errorf("Expected 2 total children, got %d", e.TotalChildren)
}
if e.ClosedChildren != 0 {
t.Errorf("Expected 0 closed children, got %d", e.ClosedChildren)
}
if e.EligibleForClose {
t.Error("Epic should NOT be eligible for closure with open children")
}
}
}
if !found {
t.Error("Epic not found in results")
}
e := h.assertEpicFound(epics, epic.ID, "All children open")
h.assertEpicStats(e, 2, 0, false, "All children open")
// Test 2: Close one task
err = store.CloseIssue(ctx, task1.ID, "Done", "test-user")
if err != nil {
t.Fatalf("CloseIssue (task1) failed: %v", err)
}
epics, err = store.GetEpicsEligibleForClosure(ctx)
if err != nil {
t.Fatalf("GetEpicsEligibleForClosure (after closing task1) failed: %v", err)
}
found = false
for _, e := range epics {
if e.Epic.ID == epic.ID {
found = true
if e.ClosedChildren != 1 {
t.Errorf("Expected 1 closed child, got %d", e.ClosedChildren)
}
if e.EligibleForClose {
t.Error("Epic should NOT be eligible with only 1/2 tasks closed")
}
}
}
if !found {
t.Error("Epic not found after closing one task")
}
h.closeIssue(task1.ID, "Done")
epics = h.getEligibleEpics()
e = h.assertEpicFound(epics, epic.ID, "One child closed")
h.assertEpicStats(e, 2, 1, false, "One child closed")
// Test 3: Close second task - epic should be eligible
err = store.CloseIssue(ctx, task2.ID, "Done", "test-user")
if err != nil {
t.Fatalf("CloseIssue (task2) failed: %v", err)
}
h.closeIssue(task2.ID, "Done")
epics = h.getEligibleEpics()
e = h.assertEpicFound(epics, epic.ID, "All children closed")
h.assertEpicStats(e, 2, 2, true, "All children closed")
epics, err = store.GetEpicsEligibleForClosure(ctx)
if err != nil {
t.Fatalf("GetEpicsEligibleForClosure (after closing task2) failed: %v", err)
}
found = false
for _, e := range epics {
if e.Epic.ID == epic.ID {
found = true
if e.ClosedChildren != 2 {
t.Errorf("Expected 2 closed children, got %d", e.ClosedChildren)
}
if !e.EligibleForClose {
t.Error("Epic SHOULD be eligible for closure with all children closed")
}
}
}
if !found {
t.Error("Epic not found after closing all tasks")
}
// Test 4: Close the epic - should no longer appear in results
err = store.CloseIssue(ctx, epic.ID, "All tasks complete", "test-user")
if err != nil {
t.Fatalf("CloseIssue (epic) failed: %v", err)
}
epics, err = store.GetEpicsEligibleForClosure(ctx)
if err != nil {
t.Fatalf("GetEpicsEligibleForClosure (after closing epic) failed: %v", err)
}
// Closed epics should not appear in results
for _, e := range epics {
if e.Epic.ID == epic.ID {
t.Error("Closed epic should not appear in eligible list")
}
}
// Test 4: Close the epic - should no longer appear
h.closeIssue(epic.ID, "All tasks complete")
epics = h.getEligibleEpics()
h.assertEpicNotFound(epics, epic.ID, "Closed epic")
}
func TestGetEpicsEligibleForClosureWithNoChildren(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
h := newEpicTestHelper(t, store)
epic := h.createEpic("Childless Epic")
epics := h.getEligibleEpics()
// Create an epic with no children
epic := &types.Issue{
Title: "Childless Epic",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeEpic,
}
err := store.CreateIssue(ctx, epic, "test-user")
if err != nil {
t.Fatalf("CreateIssue failed: %v", err)
}
epics, err := store.GetEpicsEligibleForClosure(ctx)
if err != nil {
t.Fatalf("GetEpicsEligibleForClosure failed: %v", err)
}
// Should find the epic but it should NOT be eligible (no children = not eligible)
found := false
for _, e := range epics {
if e.Epic.ID == epic.ID {
found = true
if e.TotalChildren != 0 {
t.Errorf("Expected 0 total children, got %d", e.TotalChildren)
}
if e.EligibleForClose {
t.Error("Epic with no children should NOT be eligible for closure")
}
}
}
if !found {
t.Error("Epic not found in results")
}
// Should find the epic but it should NOT be eligible
e := h.assertEpicFound(epics, epic.ID, "No children")
h.assertEpicStats(e, 0, 0, false, "No children")
}

View File

@@ -192,313 +192,248 @@ func TestGetIssueNotFound(t *testing.T) {
}
}
// createIssuesTestHelper provides test setup and assertion methods
type createIssuesTestHelper struct {
t *testing.T
ctx context.Context
store *SQLiteStorage
}
func newCreateIssuesHelper(t *testing.T, store *SQLiteStorage) *createIssuesTestHelper {
return &createIssuesTestHelper{t: t, ctx: context.Background(), store: store}
}
func (h *createIssuesTestHelper) newIssue(id, title string, status types.Status, priority int, issueType types.IssueType, closedAt *time.Time) *types.Issue {
return &types.Issue{
ID: id,
Title: title,
Status: status,
Priority: priority,
IssueType: issueType,
ClosedAt: closedAt,
}
}
func (h *createIssuesTestHelper) createIssues(issues []*types.Issue) error {
return h.store.CreateIssues(h.ctx, issues, "test-user")
}
func (h *createIssuesTestHelper) assertNoError(err error) {
if err != nil {
h.t.Errorf("CreateIssues() unexpected error: %v", err)
}
}
func (h *createIssuesTestHelper) assertError(err error) {
if err == nil {
h.t.Error("CreateIssues() expected error, got nil")
}
}
func (h *createIssuesTestHelper) assertCount(issues []*types.Issue, expected int) {
if len(issues) != expected {
h.t.Errorf("expected %d issues, got %d", expected, len(issues))
}
}
func (h *createIssuesTestHelper) assertIDSet(issue *types.Issue, index int) {
if issue.ID == "" {
h.t.Errorf("issue %d: ID should be set", index)
}
}
func (h *createIssuesTestHelper) assertTimestampSet(ts time.Time, field string, index int) {
if !ts.After(time.Time{}) {
h.t.Errorf("issue %d: %s should be set", index, field)
}
}
func (h *createIssuesTestHelper) assertUniqueIDs(issues []*types.Issue) {
ids := make(map[string]bool)
for _, issue := range issues {
if ids[issue.ID] {
h.t.Errorf("duplicate ID found: %s", issue.ID)
}
ids[issue.ID] = true
}
}
func (h *createIssuesTestHelper) assertEqual(expected, actual interface{}, field string) {
if expected != actual {
h.t.Errorf("expected %s %v, got %v", field, expected, actual)
}
}
func (h *createIssuesTestHelper) assertNotNil(value interface{}, field string) {
if value == nil {
h.t.Errorf("%s should be set", field)
}
}
func (h *createIssuesTestHelper) assertNoAutoGenID(issues []*types.Issue, wantErr bool) {
if !wantErr {
return
}
for i, issue := range issues {
if issue == nil {
continue
}
hasCustomID := issue.ID != "" && (issue.ID == "custom-1" || issue.ID == "custom-2" ||
issue.ID == "duplicate-id" || issue.ID == "existing-id")
if !hasCustomID && issue.ID != "" {
h.t.Errorf("issue %d: ID should not be auto-generated on error, got %s", i, issue.ID)
}
}
}
func TestCreateIssues(t *testing.T) {
store, cleanup := setupTestDB(t)
defer cleanup()
ctx := context.Background()
h := newCreateIssuesHelper(t, store)
tests := []struct {
name string
issues []*types.Issue
wantErr bool
checkFunc func(t *testing.T, issues []*types.Issue)
checkFunc func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue)
}{
{
name: "empty batch",
issues: []*types.Issue{},
wantErr: false,
checkFunc: func(t *testing.T, issues []*types.Issue) {
if len(issues) != 0 {
t.Errorf("expected 0 issues, got %d", len(issues))
}
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {
h.assertCount(issues, 0)
},
},
{
name: "single issue",
issues: []*types.Issue{
{
Title: "Single issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
h.newIssue("", "Single issue", types.StatusOpen, 1, types.TypeTask, nil),
},
wantErr: false,
checkFunc: func(t *testing.T, issues []*types.Issue) {
if len(issues) != 1 {
t.Fatalf("expected 1 issue, got %d", len(issues))
}
if issues[0].ID == "" {
t.Error("issue ID should be set")
}
if !issues[0].CreatedAt.After(time.Time{}) {
t.Error("CreatedAt should be set")
}
if !issues[0].UpdatedAt.After(time.Time{}) {
t.Error("UpdatedAt should be set")
}
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {
h.assertCount(issues, 1)
h.assertIDSet(issues[0], 0)
h.assertTimestampSet(issues[0].CreatedAt, "CreatedAt", 0)
h.assertTimestampSet(issues[0].UpdatedAt, "UpdatedAt", 0)
},
},
{
name: "multiple issues",
issues: []*types.Issue{
{
Title: "Issue 1",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
{
Title: "Issue 2",
Status: types.StatusInProgress,
Priority: 2,
IssueType: types.TypeBug,
},
{
Title: "Issue 3",
Status: types.StatusOpen,
Priority: 3,
IssueType: types.TypeFeature,
},
h.newIssue("", "Issue 1", types.StatusOpen, 1, types.TypeTask, nil),
h.newIssue("", "Issue 2", types.StatusInProgress, 2, types.TypeBug, nil),
h.newIssue("", "Issue 3", types.StatusOpen, 3, types.TypeFeature, nil),
},
wantErr: false,
checkFunc: func(t *testing.T, issues []*types.Issue) {
if len(issues) != 3 {
t.Fatalf("expected 3 issues, got %d", len(issues))
}
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {
h.assertCount(issues, 3)
for i, issue := range issues {
if issue.ID == "" {
t.Errorf("issue %d: ID should be set", i)
}
if !issue.CreatedAt.After(time.Time{}) {
t.Errorf("issue %d: CreatedAt should be set", i)
}
if !issue.UpdatedAt.After(time.Time{}) {
t.Errorf("issue %d: UpdatedAt should be set", i)
}
}
// Verify IDs are unique
ids := make(map[string]bool)
for _, issue := range issues {
if ids[issue.ID] {
t.Errorf("duplicate ID found: %s", issue.ID)
}
ids[issue.ID] = true
h.assertIDSet(issue, i)
h.assertTimestampSet(issue.CreatedAt, "CreatedAt", i)
h.assertTimestampSet(issue.UpdatedAt, "UpdatedAt", i)
}
h.assertUniqueIDs(issues)
},
},
{
name: "mixed ID assignment - explicit and auto-generated",
issues: []*types.Issue{
{
ID: "custom-1",
Title: "Custom ID 1",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
{
Title: "Auto ID",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
{
ID: "custom-2",
Title: "Custom ID 2",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
h.newIssue("custom-1", "Custom ID 1", types.StatusOpen, 1, types.TypeTask, nil),
h.newIssue("", "Auto ID", types.StatusOpen, 1, types.TypeTask, nil),
h.newIssue("custom-2", "Custom ID 2", types.StatusOpen, 1, types.TypeTask, nil),
},
wantErr: false,
checkFunc: func(t *testing.T, issues []*types.Issue) {
if len(issues) != 3 {
t.Fatalf("expected 3 issues, got %d", len(issues))
}
if issues[0].ID != "custom-1" {
t.Errorf("expected ID 'custom-1', got %s", issues[0].ID)
}
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {
h.assertCount(issues, 3)
h.assertEqual("custom-1", issues[0].ID, "ID")
if issues[1].ID == "" || issues[1].ID == "custom-1" || issues[1].ID == "custom-2" {
t.Errorf("expected auto-generated ID, got %s", issues[1].ID)
}
if issues[2].ID != "custom-2" {
t.Errorf("expected ID 'custom-2', got %s", issues[2].ID)
}
h.assertEqual("custom-2", issues[2].ID, "ID")
},
},
{
name: "validation error - missing title",
issues: []*types.Issue{
{
Title: "Valid issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
{
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
h.newIssue("", "Valid issue", types.StatusOpen, 1, types.TypeTask, nil),
h.newIssue("", "", types.StatusOpen, 1, types.TypeTask, nil),
},
wantErr: true,
checkFunc: func(t *testing.T, issues []*types.Issue) {
// Should not be called on error
},
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {},
},
{
name: "validation error - invalid priority",
issues: []*types.Issue{
{
Title: "Test",
Status: types.StatusOpen,
Priority: 10,
IssueType: types.TypeTask,
},
},
name: "validation error - invalid priority",
issues: []*types.Issue{h.newIssue("", "Test", types.StatusOpen, 10, types.TypeTask, nil)},
wantErr: true,
checkFunc: func(t *testing.T, issues []*types.Issue) {
// Should not be called on error
},
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {},
},
{
name: "validation error - invalid status",
issues: []*types.Issue{
{
Title: "Test",
Status: "invalid",
Priority: 1,
IssueType: types.TypeTask,
},
},
name: "validation error - invalid status",
issues: []*types.Issue{h.newIssue("", "Test", "invalid", 1, types.TypeTask, nil)},
wantErr: true,
checkFunc: func(t *testing.T, issues []*types.Issue) {
// Should not be called on error
},
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {},
},
{
name: "duplicate ID error",
issues: []*types.Issue{
{
ID: "duplicate-id",
Title: "First issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
{
ID: "duplicate-id",
Title: "Second issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
h.newIssue("duplicate-id", "First issue", types.StatusOpen, 1, types.TypeTask, nil),
h.newIssue("duplicate-id", "Second issue", types.StatusOpen, 1, types.TypeTask, nil),
},
wantErr: true,
checkFunc: func(t *testing.T, issues []*types.Issue) {
// Should not be called on error
},
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {},
},
{
name: "closed_at invariant - open status with closed_at",
issues: []*types.Issue{
{
Title: "Invalid closed_at",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
ClosedAt: &time.Time{},
},
h.newIssue("", "Invalid closed_at", types.StatusOpen, 1, types.TypeTask, &time.Time{}),
},
wantErr: true,
checkFunc: func(t *testing.T, issues []*types.Issue) {
// Should not be called on error
},
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {},
},
{
name: "closed_at invariant - closed status without closed_at",
issues: []*types.Issue{
{
Title: "Missing closed_at",
Status: types.StatusClosed,
Priority: 1,
IssueType: types.TypeTask,
},
h.newIssue("", "Missing closed_at", types.StatusClosed, 1, types.TypeTask, nil),
},
wantErr: true,
checkFunc: func(t *testing.T, issues []*types.Issue) {
// Should not be called on error
},
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {},
},
{
name: "nil item in batch",
issues: []*types.Issue{
{
Title: "Valid issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
},
h.newIssue("", "Valid issue", types.StatusOpen, 1, types.TypeTask, nil),
nil,
},
wantErr: true,
checkFunc: func(t *testing.T, issues []*types.Issue) {
// Should not be called on error
},
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {},
},
{
name: "valid closed issue with closed_at",
issues: []*types.Issue{
{
Title: "Properly closed",
Status: types.StatusClosed,
Priority: 1,
IssueType: types.TypeTask,
ClosedAt: func() *time.Time { t := time.Now(); return &t }(),
},
h.newIssue("", "Properly closed", types.StatusClosed, 1, types.TypeTask, func() *time.Time { t := time.Now(); return &t }()),
},
wantErr: false,
checkFunc: func(t *testing.T, issues []*types.Issue) {
if len(issues) != 1 {
t.Fatalf("expected 1 issue, got %d", len(issues))
}
if issues[0].Status != types.StatusClosed {
t.Errorf("expected closed status, got %s", issues[0].Status)
}
if issues[0].ClosedAt == nil {
t.Error("ClosedAt should be set for closed issue")
}
checkFunc: func(t *testing.T, h *createIssuesTestHelper, issues []*types.Issue) {
h.assertCount(issues, 1)
h.assertEqual(types.StatusClosed, issues[0].Status, "status")
h.assertNotNil(issues[0].ClosedAt, "ClosedAt")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := store.CreateIssues(ctx, tt.issues, "test-user")
if (err != nil) != tt.wantErr {
t.Errorf("CreateIssues() error = %v, wantErr %v", err, tt.wantErr)
return
}
err := h.createIssues(tt.issues)
if tt.wantErr {
// Verify IDs weren't auto-generated on error (timestamps may be set)
for i, issue := range tt.issues {
if issue == nil {
continue
}
// Allow pre-set IDs (custom-1, existing-id, duplicate-id, etc.)
hasCustomID := issue.ID != "" && (issue.ID == "custom-1" || issue.ID == "custom-2" ||
issue.ID == "duplicate-id" || issue.ID == "existing-id")
if !hasCustomID && issue.ID != "" {
t.Errorf("issue %d: ID should not be auto-generated on error, got %s", i, issue.ID)
}
}
h.assertError(err)
h.assertNoAutoGenID(tt.issues, tt.wantErr)
} else {
h.assertNoError(err)
}
if !tt.wantErr && tt.checkFunc != nil {
tt.checkFunc(t, tt.issues)
tt.checkFunc(t, h, tt.issues)
}
})
}