- Native Windows daemon using TCP loopback endpoints - Direct-mode fallback for CLI/daemon compatibility - Comment operations over RPC - PowerShell installer script - Go 1.24 requirement - Cross-OS testing documented Co-authored-by: danshapiro <danshapiro@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-c6230265-055f-4af1-9712-4481061886db Co-authored-by: Amp <amp@ampcode.com>
222 lines
5.7 KiB
Go
222 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/steveyegge/beads/internal/storage/sqlite"
|
|
"github.com/steveyegge/beads/internal/types"
|
|
)
|
|
|
|
func TestValidatePrefix(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
prefix string
|
|
wantErr bool
|
|
}{
|
|
{"valid lowercase", "kw-", false},
|
|
{"valid with numbers", "work1-", false},
|
|
{"valid with hyphen", "my-work-", false},
|
|
{"empty", "", true},
|
|
{"too long", "verylongprefix-", true},
|
|
{"starts with number", "1work-", true},
|
|
{"uppercase", "KW-", true},
|
|
{"no hyphen", "kw", false},
|
|
{"just hyphen", "-", true},
|
|
{"starts with hyphen", "-work", true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validatePrefix(tt.prefix)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("validatePrefix(%q) error = %v, wantErr %v", tt.prefix, err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenamePrefixCommand(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dbPath := filepath.Join(tmpDir, "test.db")
|
|
|
|
testStore, err := sqlite.New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
defer testStore.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
store = testStore
|
|
actor = "test"
|
|
defer func() {
|
|
store = nil
|
|
actor = ""
|
|
}()
|
|
|
|
if err := testStore.SetConfig(ctx, "issue_prefix", "old"); err != nil {
|
|
t.Fatalf("Failed to set config: %v", err)
|
|
}
|
|
|
|
issue1 := &types.Issue{
|
|
ID: "old-1",
|
|
Title: "Fix bug in old-2",
|
|
Description: "See old-3 for details",
|
|
Status: types.StatusOpen,
|
|
Priority: 1,
|
|
IssueType: types.TypeBug,
|
|
}
|
|
issue2 := &types.Issue{
|
|
ID: "old-2",
|
|
Title: "Related to old-1",
|
|
Description: "This depends on old-1",
|
|
Status: types.StatusOpen,
|
|
Priority: 1,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
issue3 := &types.Issue{
|
|
ID: "old-3",
|
|
Title: "Another issue",
|
|
Description: "Referenced by old-1",
|
|
Design: "Mentions old-2 in design",
|
|
Status: types.StatusOpen,
|
|
Priority: 2,
|
|
IssueType: types.TypeFeature,
|
|
}
|
|
|
|
if err := testStore.CreateIssue(ctx, issue1, "test"); err != nil {
|
|
t.Fatalf("Failed to create issue1: %v", err)
|
|
}
|
|
if err := testStore.CreateIssue(ctx, issue2, "test"); err != nil {
|
|
t.Fatalf("Failed to create issue2: %v", err)
|
|
}
|
|
if err := testStore.CreateIssue(ctx, issue3, "test"); err != nil {
|
|
t.Fatalf("Failed to create issue3: %v", err)
|
|
}
|
|
|
|
dep := &types.Dependency{
|
|
IssueID: "old-1",
|
|
DependsOnID: "old-2",
|
|
Type: types.DepBlocks,
|
|
}
|
|
if err := testStore.AddDependency(ctx, dep, "test"); err != nil {
|
|
t.Fatalf("Failed to add dependency: %v", err)
|
|
}
|
|
|
|
issues := []*types.Issue{issue1, issue2, issue3}
|
|
if err := renamePrefixInDB(ctx, "old", "new", issues); err != nil {
|
|
t.Fatalf("renamePrefixInDB failed: %v", err)
|
|
}
|
|
|
|
newPrefix, err := testStore.GetConfig(ctx, "issue_prefix")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get new prefix: %v", err)
|
|
}
|
|
if newPrefix != "new" {
|
|
t.Errorf("Expected prefix 'new', got %q", newPrefix)
|
|
}
|
|
|
|
updatedIssue1, err := testStore.GetIssue(ctx, "new-1")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get updated issue1: %v", err)
|
|
}
|
|
if updatedIssue1.Title != "Fix bug in new-2" {
|
|
t.Errorf("Expected title 'Fix bug in new-2', got %q", updatedIssue1.Title)
|
|
}
|
|
if updatedIssue1.Description != "See new-3 for details" {
|
|
t.Errorf("Expected description 'See new-3 for details', got %q", updatedIssue1.Description)
|
|
}
|
|
|
|
updatedIssue2, err := testStore.GetIssue(ctx, "new-2")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get updated issue2: %v", err)
|
|
}
|
|
if updatedIssue2.Title != "Related to new-1" {
|
|
t.Errorf("Expected title 'Related to new-1', got %q", updatedIssue2.Title)
|
|
}
|
|
if updatedIssue2.Description != "This depends on new-1" {
|
|
t.Errorf("Expected description 'This depends on new-1', got %q", updatedIssue2.Description)
|
|
}
|
|
|
|
updatedIssue3, err := testStore.GetIssue(ctx, "new-3")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get updated issue3: %v", err)
|
|
}
|
|
if updatedIssue3.Design != "Mentions new-2 in design" {
|
|
t.Errorf("Expected design 'Mentions new-2 in design', got %q", updatedIssue3.Design)
|
|
}
|
|
|
|
deps, err := testStore.GetDependencies(ctx, "new-1")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get dependencies: %v", err)
|
|
}
|
|
if len(deps) != 1 {
|
|
t.Fatalf("Expected 1 dependency, got %d", len(deps))
|
|
}
|
|
if deps[0].ID != "new-2" {
|
|
t.Errorf("Expected dependency ID 'new-2', got %q", deps[0].ID)
|
|
}
|
|
|
|
oldIssue, err := testStore.GetIssue(ctx, "old-1")
|
|
if err == nil && oldIssue != nil {
|
|
t.Errorf("Expected old-1 to not exist, but got: %+v", oldIssue)
|
|
}
|
|
}
|
|
|
|
func TestRenamePrefixInDB(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dbPath := filepath.Join(tmpDir, "test.db")
|
|
|
|
testStore, err := sqlite.New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
testStore.Close()
|
|
os.Remove(dbPath)
|
|
})
|
|
|
|
ctx := context.Background()
|
|
store = testStore
|
|
actor = "test-actor"
|
|
|
|
if err := testStore.SetConfig(ctx, "issue_prefix", "old"); err != nil {
|
|
t.Fatalf("Failed to set config: %v", err)
|
|
}
|
|
|
|
issue1 := &types.Issue{
|
|
ID: "old-1",
|
|
Title: "Test issue",
|
|
Description: "Description",
|
|
Status: types.StatusOpen,
|
|
Priority: 1,
|
|
IssueType: types.TypeTask,
|
|
}
|
|
|
|
if err := testStore.CreateIssue(ctx, issue1, "test"); err != nil {
|
|
t.Fatalf("Failed to create issue: %v", err)
|
|
}
|
|
|
|
issues := []*types.Issue{issue1}
|
|
err = renamePrefixInDB(ctx, "old", "new", issues)
|
|
if err != nil {
|
|
t.Fatalf("renamePrefixInDB failed: %v", err)
|
|
}
|
|
|
|
oldIssue, err := testStore.GetIssue(ctx, "old-1")
|
|
if err == nil && oldIssue != nil {
|
|
t.Errorf("Expected old-1 to not exist after rename, got: %+v", oldIssue)
|
|
}
|
|
|
|
newIssue, err := testStore.GetIssue(ctx, "new-1")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get new-1: %v", err)
|
|
}
|
|
if newIssue.ID != "new-1" {
|
|
t.Errorf("Expected ID 'new-1', got %q", newIssue.ID)
|
|
}
|
|
}
|