Adds infrastructure to automatically update embedded formulas when the binary is upgraded, while preserving user customizations. Changes: - Add CheckFormulaHealth() to detect outdated/modified/missing formulas - Add UpdateFormulas() to safely update formulas via gt doctor --fix - Track installed formula checksums in .beads/formulas/.installed.json - Add FormulaCheck to gt doctor with auto-fix capability - Compute checksums at runtime from embedded files (no build-time manifest) Update scenarios: - Outdated (embedded changed, user unchanged): Update automatically - Modified (user customized): Skip with warning - Missing (user deleted): Reinstall with message - New (never installed): Install 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
104 lines
2.3 KiB
Go
104 lines
2.3 KiB
Go
package doctor
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/steveyegge/gastown/internal/formula"
|
|
)
|
|
|
|
func TestNewFormulaCheck(t *testing.T) {
|
|
check := NewFormulaCheck()
|
|
if check.Name() != "formulas" {
|
|
t.Errorf("Name() = %q, want %q", check.Name(), "formulas")
|
|
}
|
|
if !check.CanFix() {
|
|
t.Error("FormulaCheck should be fixable")
|
|
}
|
|
}
|
|
|
|
func TestFormulaCheck_Run_AllOK(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Provision formulas fresh
|
|
_, err := formula.ProvisionFormulas(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("ProvisionFormulas() error: %v", err)
|
|
}
|
|
|
|
check := NewFormulaCheck()
|
|
ctx := &CheckContext{TownRoot: tmpDir}
|
|
|
|
result := check.Run(ctx)
|
|
|
|
if result.Status != StatusOK {
|
|
t.Errorf("Status = %v, want %v", result.Status, StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestFormulaCheck_Run_Missing(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Provision formulas
|
|
_, err := formula.ProvisionFormulas(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("ProvisionFormulas() error: %v", err)
|
|
}
|
|
|
|
// Delete a formula
|
|
formulasDir := filepath.Join(tmpDir, ".beads", "formulas")
|
|
formulaPath := filepath.Join(formulasDir, "mol-deacon-patrol.formula.toml")
|
|
if err := os.Remove(formulaPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
check := NewFormulaCheck()
|
|
ctx := &CheckContext{TownRoot: tmpDir}
|
|
|
|
result := check.Run(ctx)
|
|
|
|
if result.Status != StatusWarning {
|
|
t.Errorf("Status = %v, want %v", result.Status, StatusWarning)
|
|
}
|
|
if result.FixHint == "" {
|
|
t.Error("should have FixHint")
|
|
}
|
|
}
|
|
|
|
func TestFormulaCheck_Fix(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Provision formulas
|
|
_, err := formula.ProvisionFormulas(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("ProvisionFormulas() error: %v", err)
|
|
}
|
|
|
|
// Delete a formula
|
|
formulasDir := filepath.Join(tmpDir, ".beads", "formulas")
|
|
formulaPath := filepath.Join(formulasDir, "mol-deacon-patrol.formula.toml")
|
|
if err := os.Remove(formulaPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
check := NewFormulaCheck()
|
|
ctx := &CheckContext{TownRoot: tmpDir}
|
|
|
|
// Run fix
|
|
if err := check.Fix(ctx); err != nil {
|
|
t.Fatalf("Fix() error: %v", err)
|
|
}
|
|
|
|
// Verify formula was restored
|
|
if _, err := os.Stat(formulaPath); os.IsNotExist(err) {
|
|
t.Error("formula should have been restored")
|
|
}
|
|
|
|
// Re-run check - should be OK now
|
|
result := check.Run(ctx)
|
|
if result.Status != StatusOK {
|
|
t.Errorf("after fix, Status = %v, want %v", result.Status, StatusOK)
|
|
}
|
|
}
|