Files
beads/internal/importer/external_ref_test.go
Steve Yegge 1168f661d1 fix: Add context.Background() to sqlite.New() calls in test files
Multiple test files were still using the old sqlite.New(path) signature
instead of the new sqlite.New(ctx, path) signature. This was causing
compilation failures in the test suite.

Fixed files:
- internal/importer/importer_test.go
- internal/importer/external_ref_test.go
- internal/importer/timestamp_test.go
- internal/rpc/limits_test.go
- internal/rpc/list_filters_test.go
- internal/rpc/rpc_test.go
- internal/rpc/status_test.go
- internal/syncbranch/syncbranch_test.go
2025-11-21 14:48:41 -05:00

375 lines
9.7 KiB
Go

//go:build integration
// +build integration
package importer
import (
"context"
"path/filepath"
"testing"
"time"
"github.com/steveyegge/beads/internal/storage/sqlite"
"github.com/steveyegge/beads/internal/types"
)
func TestImportWithExternalRef(t *testing.T) {
if testing.Short() {
t.Skip("skipping slow import test in short mode")
}
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
// Create database
store, err := sqlite.New(context.Background(), dbPath)
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer store.Close()
// Set prefix
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set prefix: %v", err)
}
// Create initial issue with external_ref
externalRef := "JIRA-100"
initial := &types.Issue{
ID: "bd-test-1",
Title: "Initial title",
Description: "Initial description",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeBug,
ExternalRef: &externalRef,
CreatedAt: time.Now().Add(-2 * time.Hour),
UpdatedAt: time.Now().Add(-2 * time.Hour),
}
err = store.CreateIssue(ctx, initial, "test")
if err != nil {
t.Fatalf("Failed to create initial issue: %v", err)
}
// Import updated issue with same external_ref but different content
updated := &types.Issue{
ID: "bd-test-1", // Same ID
Title: "Updated title from Jira",
Description: "Updated description from Jira",
Status: types.StatusInProgress,
Priority: 2,
IssueType: types.TypeBug,
ExternalRef: &externalRef, // Same external_ref
CreatedAt: initial.CreatedAt,
UpdatedAt: time.Now(), // Newer timestamp
}
opts := Options{
DryRun: false,
SkipUpdate: false,
SkipPrefixValidation: true,
}
result, err := ImportIssues(ctx, dbPath, store, []*types.Issue{updated}, opts)
if err != nil {
t.Fatalf("ImportIssues failed: %v", err)
}
// Should have updated 1 issue
if result.Updated != 1 {
t.Errorf("Expected 1 updated issue, got %d", result.Updated)
}
if result.Created != 0 {
t.Errorf("Expected 0 created issues, got %d", result.Created)
}
// Verify the update
issue, err := store.GetIssue(ctx, "bd-test-1")
if err != nil {
t.Fatalf("Failed to get issue: %v", err)
}
if issue.Title != "Updated title from Jira" {
t.Errorf("Expected title 'Updated title from Jira', got '%s'", issue.Title)
}
if issue.Status != types.StatusInProgress {
t.Errorf("Expected status in_progress, got %s", issue.Status)
}
}
func TestImportWithExternalRefDifferentID(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
// Create database
store, err := sqlite.New(context.Background(), dbPath)
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer store.Close()
// Set prefix
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set prefix: %v", err)
}
// Create initial issue with external_ref
externalRef := "GH-200"
initial := &types.Issue{
ID: "bd-old-id",
Title: "Initial title",
Description: "Initial description",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeFeature,
ExternalRef: &externalRef,
CreatedAt: time.Now().Add(-2 * time.Hour),
UpdatedAt: time.Now().Add(-2 * time.Hour),
}
err = store.CreateIssue(ctx, initial, "test")
if err != nil {
t.Fatalf("Failed to create initial issue: %v", err)
}
// Import issue with same external_ref but DIFFERENT ID
// This simulates re-syncing from GitHub where ID changed
updated := &types.Issue{
ID: "bd-new-id", // Different ID
Title: "Updated title from GitHub",
Description: "Updated description from GitHub",
Status: types.StatusInProgress,
Priority: 2,
IssueType: types.TypeFeature,
ExternalRef: &externalRef, // Same external_ref
CreatedAt: initial.CreatedAt,
UpdatedAt: time.Now(), // Newer timestamp
}
opts := Options{
DryRun: false,
SkipUpdate: false,
SkipPrefixValidation: true,
}
result, err := ImportIssues(ctx, dbPath, store, []*types.Issue{updated}, opts)
if err != nil {
t.Fatalf("ImportIssues failed: %v", err)
}
// Should have updated the existing issue (matched by external_ref)
if result.Updated != 1 {
t.Errorf("Expected 1 updated issue, got %d", result.Updated)
}
// Verify the old ID was updated (not deleted/recreated)
oldIssue, err := store.GetIssue(ctx, "bd-old-id")
if err != nil {
t.Fatalf("Failed to get issue by old ID: %v", err)
}
if oldIssue == nil {
t.Fatal("Expected old ID to still exist and be updated")
}
if oldIssue.Title != "Updated title from GitHub" {
t.Errorf("Expected title 'Updated title from GitHub', got '%s'", oldIssue.Title)
}
// The new ID should NOT exist (we updated the existing one)
newIssue, err := store.GetIssue(ctx, "bd-new-id")
if err != nil {
t.Fatalf("Failed to check for new ID: %v", err)
}
if newIssue != nil {
t.Error("Expected new ID to NOT be created, but it exists")
}
}
func TestImportLocalIssueNotOverwrittenByExternalRef(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
// Create database
store, err := sqlite.New(context.Background(), dbPath)
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer store.Close()
// Set prefix
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set prefix: %v", err)
}
// Create local issue WITHOUT external_ref
local := &types.Issue{
ID: "bd-local-1",
Title: "Local task",
Description: "Created locally",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
// No ExternalRef
CreatedAt: time.Now().Add(-2 * time.Hour),
UpdatedAt: time.Now().Add(-2 * time.Hour),
}
err = store.CreateIssue(ctx, local, "test")
if err != nil {
t.Fatalf("Failed to create local issue: %v", err)
}
// Import external issue with external_ref but different ID
externalRef := "JIRA-300"
external := &types.Issue{
ID: "bd-external-1",
Title: "External issue",
Description: "From Jira",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeBug,
ExternalRef: &externalRef,
CreatedAt: time.Now().Add(-1 * time.Hour),
UpdatedAt: time.Now().Add(-1 * time.Hour),
}
opts := Options{
DryRun: false,
SkipUpdate: false,
SkipPrefixValidation: true,
}
result, err := ImportIssues(ctx, dbPath, store, []*types.Issue{external}, opts)
if err != nil {
t.Fatalf("ImportIssues failed: %v", err)
}
// Should create new issue (not overwrite local one)
if result.Created != 1 {
t.Errorf("Expected 1 created issue, got %d", result.Created)
}
// Verify local issue still exists unchanged
localIssue, err := store.GetIssue(ctx, "bd-local-1")
if err != nil {
t.Fatalf("Failed to get local issue: %v", err)
}
if localIssue == nil {
t.Fatal("Local issue was deleted!")
}
if localIssue.Title != "Local task" {
t.Errorf("Local issue was modified! Title: %s", localIssue.Title)
}
if localIssue.ExternalRef != nil {
t.Error("Local issue should not have external_ref")
}
// Verify external issue was created
externalIssue, err := store.GetIssue(ctx, "bd-external-1")
if err != nil {
t.Fatalf("Failed to get external issue: %v", err)
}
if externalIssue == nil {
t.Fatal("External issue was not created")
}
if externalIssue.ExternalRef == nil || *externalIssue.ExternalRef != externalRef {
t.Error("External issue missing external_ref")
}
}
func TestImportExternalRefTimestampCheck(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
// Create database
store, err := sqlite.New(context.Background(), dbPath)
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer store.Close()
// Set prefix
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
t.Fatalf("Failed to set prefix: %v", err)
}
// Create issue with external_ref and recent timestamp
externalRef := "LINEAR-400"
recent := &types.Issue{
ID: "bd-test-1",
Title: "Recent version",
Description: "Most recent",
Status: types.StatusInProgress,
Priority: 1,
IssueType: types.TypeBug,
ExternalRef: &externalRef,
CreatedAt: time.Now().Add(-1 * time.Hour),
UpdatedAt: time.Now(), // Recent
}
err = store.CreateIssue(ctx, recent, "test")
if err != nil {
t.Fatalf("Failed to create recent issue: %v", err)
}
// Try to import older version with same external_ref
older := &types.Issue{
ID: "bd-test-1",
Title: "Older version",
Description: "Older",
Status: types.StatusOpen,
Priority: 2,
IssueType: types.TypeBug,
ExternalRef: &externalRef,
CreatedAt: time.Now().Add(-2 * time.Hour),
UpdatedAt: time.Now().Add(-2 * time.Hour), // Older
}
opts := Options{
DryRun: false,
SkipUpdate: false,
SkipPrefixValidation: true,
}
result, err := ImportIssues(ctx, dbPath, store, []*types.Issue{older}, opts)
if err != nil {
t.Fatalf("ImportIssues failed: %v", err)
}
// Should NOT update (incoming is older)
if result.Updated != 0 {
t.Errorf("Expected 0 updated issues (timestamp check), got %d", result.Updated)
}
if result.Unchanged != 1 {
t.Errorf("Expected 1 unchanged issue, got %d", result.Unchanged)
}
// Verify the issue was not changed
issue, err := store.GetIssue(ctx, "bd-test-1")
if err != nil {
t.Fatalf("Failed to get issue: %v", err)
}
if issue.Title != "Recent version" {
t.Errorf("Issue was updated when it shouldn't be! Title: %s", issue.Title)
}
if issue.Status != types.StatusInProgress {
t.Errorf("Issue status changed! Got %s", issue.Status)
}
}