* fix(import): support custom issue types during import Fixes regression from7cf67153where custom issue types (agent, molecule, convoy, etc.) were rejected during import with "invalid issue type" error. - Add validateFieldUpdateWithCustom() for both custom statuses and types - Add validateIssueTypeWithCustom() for custom type validation - Update queries.go UpdateIssue() to fetch and validate custom types - Update transaction.go UpdateIssue() to fetch and validate custom types - Add 15 test cases covering custom type validation scenarios This aligns UpdateIssue() validation with the federation trust model used by ValidateForImport(). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(test): add metadata.json and chdir to temp dir in direct mode tests Fixes three test failures caused by commite82f5136which changed ensureStoreActive() to use factory.NewFromConfig() instead of respecting the global dbPath variable. Root cause: - Tests create issues in test.db and set dbPath = testDBPath - ensureStoreActive() calls factory.NewFromConfig() which reads metadata.json - Without metadata.json, it defaults to beads.db - Opens empty beads.db instead of test.db with the seeded issues - Additionally, FindBeadsDir() was finding the real .beads dir, not the test one Fixes applied: 1. TestFallbackToDirectModeEnablesFlush: Add metadata.json pointing to test.db and chdir to temp dir 2. TestImportFromJSONLInlineAfterDaemonDisconnect: Same fix 3. TestIsBeadsPluginInstalledProjectLevel: Set temp HOME to avoid detecting plugin from real ~/.claude/settings.json All three tests now pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
119 lines
4.0 KiB
Go
119 lines
4.0 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/steveyegge/beads/internal/types"
|
|
)
|
|
|
|
// validatePriority validates a priority value
|
|
func validatePriority(value interface{}) error {
|
|
if priority, ok := value.(int); ok {
|
|
if priority < 0 || priority > 4 {
|
|
return fmt.Errorf("priority must be between 0 and 4 (got %d)", priority)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateStatus validates a status value (built-in statuses only)
|
|
func validateStatus(value interface{}) error {
|
|
return validateStatusWithCustom(value, nil)
|
|
}
|
|
|
|
// validateStatusWithCustom validates a status value, allowing custom statuses.
|
|
// Note: tombstone status is blocked here (bd-y68) - use bd delete instead of bd update --status=tombstone
|
|
func validateStatusWithCustom(value interface{}, customStatuses []string) error {
|
|
if status, ok := value.(string); ok {
|
|
// Block direct status update to tombstone (bd-y68)
|
|
// Tombstones should only be created via bd delete, not bd update --status=tombstone
|
|
if types.Status(status) == types.StatusTombstone {
|
|
return fmt.Errorf("cannot set status to tombstone directly; use 'bd delete' instead")
|
|
}
|
|
if !types.Status(status).IsValidWithCustom(customStatuses) {
|
|
return fmt.Errorf("invalid status: %s", status)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateIssueType validates an issue type value
|
|
func validateIssueType(value interface{}) error {
|
|
if issueType, ok := value.(string); ok {
|
|
// Normalize first to support aliases like "enhancement" -> "feature"
|
|
if !types.IssueType(issueType).Normalize().IsValid() {
|
|
return fmt.Errorf("invalid issue type: %s", issueType)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateTitle validates a title value
|
|
func validateTitle(value interface{}) error {
|
|
if title, ok := value.(string); ok {
|
|
if len(title) == 0 || len(title) > 500 {
|
|
return fmt.Errorf("title must be 1-500 characters")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateEstimatedMinutes validates an estimated_minutes value
|
|
func validateEstimatedMinutes(value interface{}) error {
|
|
if mins, ok := value.(int); ok {
|
|
if mins < 0 {
|
|
return fmt.Errorf("estimated_minutes cannot be negative")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// fieldValidators maps field names to their validation functions
|
|
var fieldValidators = map[string]func(interface{}) error{
|
|
"priority": validatePriority,
|
|
"status": validateStatus,
|
|
"issue_type": validateIssueType,
|
|
"title": validateTitle,
|
|
"estimated_minutes": validateEstimatedMinutes,
|
|
}
|
|
|
|
// validateFieldUpdate validates a field update value (built-in statuses only)
|
|
func validateFieldUpdate(key string, value interface{}) error {
|
|
return validateFieldUpdateWithCustomStatuses(key, value, nil)
|
|
}
|
|
|
|
// validateFieldUpdateWithCustom validates a field update value,
|
|
// allowing custom statuses and custom types for their respective field validations.
|
|
func validateFieldUpdateWithCustom(key string, value interface{}, customStatuses, customTypes []string) error {
|
|
// Special handling for status field to support custom statuses
|
|
if key == "status" {
|
|
return validateStatusWithCustom(value, customStatuses)
|
|
}
|
|
// Special handling for issue_type field to support custom types (federation trust model)
|
|
if key == "issue_type" {
|
|
return validateIssueTypeWithCustom(value, customTypes)
|
|
}
|
|
if validator, ok := fieldValidators[key]; ok {
|
|
return validator(value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateFieldUpdateWithCustomStatuses validates a field update value,
|
|
// allowing custom statuses for status field validation.
|
|
func validateFieldUpdateWithCustomStatuses(key string, value interface{}, customStatuses []string) error {
|
|
return validateFieldUpdateWithCustom(key, value, customStatuses, nil)
|
|
}
|
|
|
|
// validateIssueTypeWithCustom validates an issue type value, allowing custom types.
|
|
func validateIssueTypeWithCustom(value interface{}, customTypes []string) error {
|
|
if issueType, ok := value.(string); ok {
|
|
// Normalize first to support aliases like "enhancement" -> "feature"
|
|
normalized := types.IssueType(issueType).Normalize()
|
|
if !normalized.IsValidWithCustom(customTypes) {
|
|
return fmt.Errorf("invalid issue type: %s", issueType)
|
|
}
|
|
}
|
|
return nil
|
|
}
|