Short tests were timing out after 13+ minutes due to: 1. TestZFCSkipsExportAfterImport spawning subprocess that tried to auto-start daemon - now skipped in short mode 2. TestVersionFlag taking 5+ seconds because --version flag did not skip PersistentPreRun daemon startup - added early return 3. TestGetVersionsSince* had hardcoded version expectations that became stale - made tests dynamic using versionChanges array Short tests now complete in ~8 seconds instead of timing out. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
476 lines
14 KiB
Go
476 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/steveyegge/beads/internal/configfile"
|
|
"github.com/steveyegge/beads/internal/storage/sqlite"
|
|
)
|
|
|
|
func TestGetVersionsSince(t *testing.T) {
|
|
// Get current version counts dynamically from versionChanges
|
|
latestVersion := versionChanges[0].Version // First element is latest
|
|
oldestVersion := versionChanges[len(versionChanges)-1].Version // Last element is oldest
|
|
versionsAfterOldest := len(versionChanges) - 1 // All except oldest
|
|
|
|
tests := []struct {
|
|
name string
|
|
sinceVersion string
|
|
expectedCount int
|
|
description string
|
|
}{
|
|
{
|
|
name: "empty version returns all",
|
|
sinceVersion: "",
|
|
expectedCount: len(versionChanges),
|
|
description: "Should return all versions when sinceVersion is empty",
|
|
},
|
|
{
|
|
name: "version not in changelog",
|
|
sinceVersion: "0.1.0",
|
|
expectedCount: len(versionChanges),
|
|
description: "Should return all versions when sinceVersion not found",
|
|
},
|
|
{
|
|
name: "oldest version in changelog",
|
|
sinceVersion: oldestVersion,
|
|
expectedCount: versionsAfterOldest,
|
|
description: "Should return versions newer than oldest",
|
|
},
|
|
{
|
|
name: "latest version returns empty",
|
|
sinceVersion: latestVersion,
|
|
expectedCount: 0,
|
|
description: "Should return empty slice when already on latest in changelog",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := getVersionsSince(tt.sinceVersion)
|
|
if len(result) != tt.expectedCount {
|
|
t.Errorf("getVersionsSince(%q) returned %d versions, want %d: %s",
|
|
tt.sinceVersion, len(result), tt.expectedCount, tt.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetVersionsSinceOrder(t *testing.T) {
|
|
// Test that versions are returned in chronological order (oldest first)
|
|
// versionChanges array is newest-first, but getVersionsSince returns oldest-first
|
|
oldestVersion := versionChanges[len(versionChanges)-1].Version
|
|
result := getVersionsSince(oldestVersion)
|
|
|
|
expectedCount := len(versionChanges) - 1
|
|
if len(result) != expectedCount {
|
|
t.Fatalf("Expected %d versions after %s, got %d", expectedCount, oldestVersion, len(result))
|
|
}
|
|
|
|
// Verify chronological order by checking dates increase (or are equal for same-day releases)
|
|
for i := 1; i < len(result); i++ {
|
|
prev := result[i-1]
|
|
curr := result[i]
|
|
|
|
// Simple date comparison (YYYY-MM-DD format)
|
|
if curr.Date < prev.Date {
|
|
t.Errorf("Versions not in chronological order: %s (%s) should come before %s (%s)",
|
|
prev.Version, prev.Date, curr.Version, curr.Date)
|
|
}
|
|
}
|
|
|
|
// First version after oldest should be second-to-last in versionChanges
|
|
// Last version should be the first in versionChanges (latest)
|
|
if len(result) > 0 {
|
|
expectedFirst := versionChanges[len(versionChanges)-2].Version
|
|
expectedLast := versionChanges[0].Version
|
|
if result[0].Version != expectedFirst {
|
|
t.Errorf("First version = %s, want %s", result[0].Version, expectedFirst)
|
|
}
|
|
if result[len(result)-1].Version != expectedLast {
|
|
t.Errorf("Last version = %s, want %s", result[len(result)-1].Version, expectedLast)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTrackBdVersion_NoBeadsDir(t *testing.T) {
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
origPreviousVersion := previousVersion
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
previousVersion = origPreviousVersion
|
|
}()
|
|
|
|
// Change to temp directory with no .beads
|
|
tmpDir := t.TempDir()
|
|
origWd, _ := os.Getwd()
|
|
defer os.Chdir(origWd)
|
|
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatalf("Failed to change to temp dir: %v", err)
|
|
}
|
|
|
|
// trackBdVersion should silently succeed
|
|
trackBdVersion()
|
|
|
|
// Should not detect upgrade when no .beads dir exists
|
|
if versionUpgradeDetected {
|
|
t.Error("Expected no upgrade detection when .beads directory doesn't exist")
|
|
}
|
|
}
|
|
|
|
func TestTrackBdVersion_FirstRun(t *testing.T) {
|
|
// Create temp .beads directory
|
|
tmpDir := t.TempDir()
|
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create .beads: %v", err)
|
|
}
|
|
|
|
// Change to temp directory
|
|
origWd, _ := os.Getwd()
|
|
defer os.Chdir(origWd)
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatalf("Failed to change to temp dir: %v", err)
|
|
}
|
|
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
origPreviousVersion := previousVersion
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
previousVersion = origPreviousVersion
|
|
}()
|
|
|
|
// Reset state
|
|
versionUpgradeDetected = false
|
|
previousVersion = ""
|
|
|
|
// trackBdVersion should create metadata.json
|
|
trackBdVersion()
|
|
|
|
// Should not detect upgrade on first run
|
|
if versionUpgradeDetected {
|
|
t.Error("Expected no upgrade detection on first run")
|
|
}
|
|
|
|
// Should have created metadata.json with current version
|
|
cfg, err := configfile.Load(beadsDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load config after tracking: %v", err)
|
|
}
|
|
if cfg.LastBdVersion != Version {
|
|
t.Errorf("LastBdVersion = %q, want %q", cfg.LastBdVersion, Version)
|
|
}
|
|
}
|
|
|
|
func TestTrackBdVersion_UpgradeDetection(t *testing.T) {
|
|
// Create temp .beads directory
|
|
tmpDir := t.TempDir()
|
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create .beads: %v", err)
|
|
}
|
|
|
|
// Change to temp directory
|
|
origWd, _ := os.Getwd()
|
|
defer os.Chdir(origWd)
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatalf("Failed to change to temp dir: %v", err)
|
|
}
|
|
|
|
// Create metadata.json with old version
|
|
cfg := configfile.DefaultConfig()
|
|
cfg.LastBdVersion = "0.22.0"
|
|
if err := cfg.Save(beadsDir); err != nil {
|
|
t.Fatalf("Failed to save config: %v", err)
|
|
}
|
|
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
origPreviousVersion := previousVersion
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
previousVersion = origPreviousVersion
|
|
}()
|
|
|
|
// Reset state
|
|
versionUpgradeDetected = false
|
|
previousVersion = ""
|
|
|
|
// trackBdVersion should detect upgrade
|
|
trackBdVersion()
|
|
|
|
// Should detect upgrade
|
|
if !versionUpgradeDetected {
|
|
t.Error("Expected upgrade detection when version changed")
|
|
}
|
|
|
|
if previousVersion != "0.22.0" {
|
|
t.Errorf("previousVersion = %q, want %q", previousVersion, "0.22.0")
|
|
}
|
|
|
|
// Should have updated metadata.json to current version
|
|
cfg, err := configfile.Load(beadsDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load config after tracking: %v", err)
|
|
}
|
|
if cfg.LastBdVersion != Version {
|
|
t.Errorf("LastBdVersion = %q, want %q", cfg.LastBdVersion, Version)
|
|
}
|
|
}
|
|
|
|
func TestTrackBdVersion_SameVersion(t *testing.T) {
|
|
// Create temp .beads directory
|
|
tmpDir := t.TempDir()
|
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
|
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create .beads: %v", err)
|
|
}
|
|
|
|
// Change to temp directory
|
|
origWd, _ := os.Getwd()
|
|
defer os.Chdir(origWd)
|
|
if err := os.Chdir(tmpDir); err != nil {
|
|
t.Fatalf("Failed to change to temp dir: %v", err)
|
|
}
|
|
|
|
// Create metadata.json with current version
|
|
cfg := configfile.DefaultConfig()
|
|
cfg.LastBdVersion = Version
|
|
if err := cfg.Save(beadsDir); err != nil {
|
|
t.Fatalf("Failed to save config: %v", err)
|
|
}
|
|
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
origPreviousVersion := previousVersion
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
previousVersion = origPreviousVersion
|
|
}()
|
|
|
|
// Reset state
|
|
versionUpgradeDetected = false
|
|
previousVersion = ""
|
|
|
|
// trackBdVersion should not detect upgrade
|
|
trackBdVersion()
|
|
|
|
// Should not detect upgrade
|
|
if versionUpgradeDetected {
|
|
t.Error("Expected no upgrade detection when version is the same")
|
|
}
|
|
}
|
|
|
|
func TestMaybeShowUpgradeNotification(t *testing.T) {
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
origPreviousVersion := previousVersion
|
|
origUpgradeAcknowledged := upgradeAcknowledged
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
previousVersion = origPreviousVersion
|
|
upgradeAcknowledged = origUpgradeAcknowledged
|
|
}()
|
|
|
|
// Test: No upgrade detected - should not modify acknowledged flag
|
|
versionUpgradeDetected = false
|
|
upgradeAcknowledged = false
|
|
previousVersion = ""
|
|
|
|
maybeShowUpgradeNotification()
|
|
if upgradeAcknowledged {
|
|
t.Error("Should not set acknowledged flag when no upgrade detected")
|
|
}
|
|
|
|
// Test: Upgrade detected but already acknowledged - should not change state
|
|
versionUpgradeDetected = true
|
|
upgradeAcknowledged = true
|
|
previousVersion = "0.22.0"
|
|
|
|
maybeShowUpgradeNotification()
|
|
if !upgradeAcknowledged {
|
|
t.Error("Should keep acknowledged flag when already acknowledged")
|
|
}
|
|
|
|
// Test: Upgrade detected and not acknowledged - should set acknowledged flag
|
|
versionUpgradeDetected = true
|
|
upgradeAcknowledged = false
|
|
previousVersion = "0.22.0"
|
|
|
|
maybeShowUpgradeNotification()
|
|
if !upgradeAcknowledged {
|
|
t.Error("Should mark as acknowledged after showing notification")
|
|
}
|
|
|
|
// Calling again should keep acknowledged flag set
|
|
prevAck := upgradeAcknowledged
|
|
maybeShowUpgradeNotification()
|
|
if upgradeAcknowledged != prevAck {
|
|
t.Error("Should not change acknowledged state on subsequent calls")
|
|
}
|
|
}
|
|
|
|
func TestAutoMigrateOnVersionBump_NoUpgrade(t *testing.T) {
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
}()
|
|
|
|
// Reset state - no upgrade detected
|
|
versionUpgradeDetected = false
|
|
|
|
// Should return early without doing anything
|
|
autoMigrateOnVersionBump("/tmp/test.db")
|
|
|
|
// Test passes if no panic occurs
|
|
}
|
|
|
|
func TestAutoMigrateOnVersionBump_NoDatabase(t *testing.T) {
|
|
// Create temp directory
|
|
tmpDir := t.TempDir()
|
|
nonExistentDB := filepath.Join(tmpDir, "nonexistent.db")
|
|
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
}()
|
|
|
|
// Simulate version upgrade
|
|
versionUpgradeDetected = true
|
|
|
|
// Should handle gracefully when database doesn't exist
|
|
autoMigrateOnVersionBump(nonExistentDB)
|
|
|
|
// Test passes if no panic occurs
|
|
}
|
|
|
|
func TestAutoMigrateOnVersionBump_MigratesVersion(t *testing.T) {
|
|
// NOTE: Cannot use t.Parallel() because we modify global variables
|
|
|
|
// Save original state FIRST - critical to avoid test pollution from previous tests
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
origUpgradeAcknowledged := upgradeAcknowledged
|
|
origPreviousVersion := previousVersion
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
upgradeAcknowledged = origUpgradeAcknowledged
|
|
previousVersion = origPreviousVersion
|
|
}()
|
|
|
|
// Create temp directory with unique name to avoid any possible interference
|
|
tmpDir := t.TempDir()
|
|
dbPath := filepath.Join(tmpDir, "test-migrate-version-unique.db")
|
|
|
|
// Create database with old version
|
|
ctx := context.Background()
|
|
store, err := sqlite.New(ctx, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create database: %v", err)
|
|
}
|
|
|
|
// Set old database version
|
|
oldVersion := "0.22.0"
|
|
if err := store.SetMetadata(ctx, "bd_version", oldVersion); err != nil {
|
|
t.Fatalf("Failed to set old version: %v", err)
|
|
}
|
|
if err := store.Close(); err != nil {
|
|
t.Fatalf("Failed to close database: %v", err)
|
|
}
|
|
|
|
// Simulate version upgrade (must be set to true for migration to run)
|
|
// Set this AFTER closing the database to ensure clean state
|
|
upgradeAcknowledged = false
|
|
previousVersion = oldVersion
|
|
|
|
// Verify dbPath before migration
|
|
if dbPath == "" {
|
|
t.Fatalf("dbPath is empty!")
|
|
}
|
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
|
t.Fatalf("database doesn't exist before migration: %s", dbPath)
|
|
}
|
|
|
|
// Set versionUpgradeDetected immediately before calling to avoid races
|
|
versionUpgradeDetected = true
|
|
t.Logf("Calling autoMigrateOnVersionBump with dbPath=%s, versionUpgradeDetected=%v", dbPath, versionUpgradeDetected)
|
|
// Immediately call migration while flag is still true
|
|
autoMigrateOnVersionBump(dbPath)
|
|
|
|
// Verify the flag is still true after migration
|
|
if !versionUpgradeDetected {
|
|
t.Fatalf("version Upgrade detected flag was cleared during migration")
|
|
}
|
|
|
|
// Verify database version was updated
|
|
store, err = sqlite.New(ctx, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
newVersion, err := store.GetMetadata(ctx, "bd_version")
|
|
if err != nil {
|
|
t.Fatalf("Failed to read database version: %v", err)
|
|
}
|
|
|
|
if newVersion != Version {
|
|
t.Errorf("Database version not updated: got %q, want %q (versionUpgradeDetected=%v)",
|
|
newVersion, Version, versionUpgradeDetected)
|
|
}
|
|
}
|
|
|
|
func TestAutoMigrateOnVersionBump_AlreadyMigrated(t *testing.T) {
|
|
// Create temp directory
|
|
tmpDir := t.TempDir()
|
|
dbPath := filepath.Join(tmpDir, "test.db")
|
|
|
|
// Create database with current version
|
|
ctx := context.Background()
|
|
store, err := sqlite.New(ctx, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create database: %v", err)
|
|
}
|
|
|
|
// Set current database version
|
|
if err := store.SetMetadata(ctx, "bd_version", Version); err != nil {
|
|
t.Fatalf("Failed to set version: %v", err)
|
|
}
|
|
_ = store.Close()
|
|
|
|
// Save original state
|
|
origUpgradeDetected := versionUpgradeDetected
|
|
defer func() {
|
|
versionUpgradeDetected = origUpgradeDetected
|
|
}()
|
|
|
|
// Simulate version upgrade
|
|
versionUpgradeDetected = true
|
|
|
|
// Call auto-migration - should be a no-op
|
|
autoMigrateOnVersionBump(dbPath)
|
|
|
|
// Verify database version is still current
|
|
store, err = sqlite.New(ctx, dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
currentVersion, err := store.GetMetadata(ctx, "bd_version")
|
|
if err != nil {
|
|
t.Fatalf("Failed to read database version: %v", err)
|
|
}
|
|
|
|
if currentVersion != Version {
|
|
t.Errorf("Database version changed unexpectedly: got %q, want %q", currentVersion, Version)
|
|
}
|
|
}
|