- Introduce new test suite in internal/recipe/recipe_test.go covering recipe loading, parameter overrides, safety checks, work dir resolution, file discovery, and unified patch creation. - Remove t.Parallel() from tests in cmd/changelog_test.go, cmd/root_test.go, and cmd/scaffold_test.go that modify global state (e.g., os.Args, HOME, or use os.Chdir()) to avoid race conditions and ensure test isolation.
190 lines
4.9 KiB
Go
190 lines
4.9 KiB
Go
package recipe
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLoad(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
recipePath := filepath.Join(tmpDir, "test.yaml")
|
|
|
|
content := `---
|
|
name: Test Recipe
|
|
description: A recipe for testing
|
|
version: 1.0.0
|
|
parameters:
|
|
target_file:
|
|
type: string
|
|
default: "main.go"
|
|
description: File to process
|
|
allowed_shell_commands:
|
|
- ls -la
|
|
- pwd
|
|
project_languages:
|
|
- go
|
|
extensions:
|
|
go: [".go"]
|
|
search_pattern: "func main"
|
|
---
|
|
## Execution Steps
|
|
|
|
### Step 1: Discover
|
|
**Objective:** Find files.
|
|
**Instructions:** Use discovery.
|
|
**Expected output:** List of files.
|
|
|
|
### Step 2: Refactor
|
|
**Objective:** Refactor files.
|
|
**Instructions:** Use refactoring.
|
|
**Expected output:** JSON.
|
|
|
|
This is the final summary prompt.
|
|
`
|
|
err := os.WriteFile(recipePath, []byte(content), 0600)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("successful load with defaults", func(t *testing.T) {
|
|
r, err := Load(recipePath, nil)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Test Recipe", r.Name)
|
|
assert.Equal(t, "1.0.0", r.Version)
|
|
assert.Equal(t, "main.go", r.ResolvedParams["target_file"])
|
|
assert.Len(t, r.Steps, 2)
|
|
assert.Equal(t, 1, r.Steps[0].Number)
|
|
assert.Equal(t, "Discover", r.Steps[0].Title)
|
|
assert.Equal(t, "Find files.", r.Steps[0].Objective)
|
|
assert.Equal(t, "Use discovery.", r.Steps[0].Instructions)
|
|
assert.Equal(t, "List of files.", r.Steps[0].Expected)
|
|
assert.Contains(t, r.FinalSummaryPrompt, "This is the final summary prompt.")
|
|
})
|
|
|
|
t.Run("load with parameter override", func(t *testing.T) {
|
|
params := map[string]any{"target_file": "app.go"}
|
|
r, err := Load(recipePath, params)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "app.go", r.ResolvedParams["target_file"])
|
|
})
|
|
|
|
t.Run("load unsafe command", func(t *testing.T) {
|
|
unsafeContent := `---
|
|
name: Unsafe Recipe
|
|
allowed_shell_commands:
|
|
- rm -rf /
|
|
---
|
|
## Execution Steps
|
|
### Step 1: Bad
|
|
`
|
|
unsafePath := filepath.Join(tmpDir, "unsafe.yaml")
|
|
err := os.WriteFile(unsafePath, []byte(unsafeContent), 0600)
|
|
require.NoError(t, err)
|
|
|
|
_, err = Load(unsafePath, nil)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unsafe shell command")
|
|
})
|
|
|
|
t.Run("missing frontmatter", func(t *testing.T) {
|
|
badPath := filepath.Join(tmpDir, "bad.yaml")
|
|
err := os.WriteFile(badPath, []byte("no frontmatter here"), 0600)
|
|
require.NoError(t, err)
|
|
|
|
_, err = Load(badPath, nil)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "missing YAML frontmatter")
|
|
})
|
|
}
|
|
|
|
func TestResolveWorkDir(t *testing.T) {
|
|
// Not using t.Parallel() because it might depend on HOME env which we might want to mock if needed.
|
|
|
|
r := &Recipe{
|
|
ResolvedParams: make(map[string]any),
|
|
}
|
|
runner := &Runner{Recipe: r}
|
|
|
|
t.Run("default to dot", func(t *testing.T) {
|
|
dir := runner.resolveWorkDir()
|
|
absDot, _ := filepath.Abs(".")
|
|
assert.Equal(t, absDot, dir)
|
|
})
|
|
|
|
t.Run("explicit path", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
r.ResolvedParams["package_path"] = tmpDir
|
|
dir := runner.resolveWorkDir()
|
|
absTmp, _ := filepath.Abs(tmpDir)
|
|
assert.Equal(t, absTmp, dir)
|
|
})
|
|
|
|
t.Run("tilde expansion", func(t *testing.T) {
|
|
r.ResolvedParams["package_path"] = "~/projects"
|
|
dir := runner.resolveWorkDir()
|
|
home, _ := os.UserHomeDir()
|
|
expected := filepath.Join(home, "projects")
|
|
assert.Equal(t, expected, dir)
|
|
})
|
|
}
|
|
|
|
func TestDiscoverFiles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create some files
|
|
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "main.go"), []byte("func main() { if err != nil { return } }"), 0600))
|
|
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "app.go"), []byte("package main"), 0600))
|
|
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README.md"), []byte("if err != nil"), 0600))
|
|
|
|
r := &Recipe{
|
|
ProjectLanguages: []string{"go"},
|
|
Extensions: map[string][]string{"go": {".go"}},
|
|
SearchPattern: "if err != nil",
|
|
}
|
|
runner := &Runner{Recipe: r}
|
|
|
|
t.Run("finds matching files", func(t *testing.T) {
|
|
files := runner.discoverFiles(tmpDir)
|
|
assert.Len(t, files, 1)
|
|
assert.Contains(t, files[0], "main.go")
|
|
})
|
|
|
|
t.Run("no matching files", func(t *testing.T) {
|
|
r.SearchPattern = "nonexistent"
|
|
files := runner.discoverFiles(tmpDir)
|
|
assert.Len(t, files, 1)
|
|
assert.Equal(t, "No files found matching the criteria.", files[0])
|
|
})
|
|
}
|
|
|
|
func TestCreateUnifiedPatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
patchPath := filepath.Join(tmpDir, "test.patch")
|
|
|
|
changes := []FileChange{
|
|
{
|
|
File: "main.go",
|
|
Content: "package main\n\nfunc main() {}\n",
|
|
},
|
|
}
|
|
|
|
err := createUnifiedPatch(changes, patchPath)
|
|
require.NoError(t, err)
|
|
|
|
content, err := os.ReadFile(patchPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Contains(t, string(content), "--- main.go")
|
|
assert.Contains(t, string(content), "+++ main.go")
|
|
assert.Contains(t, string(content), "+package main")
|
|
assert.Contains(t, string(content), "+func main() {}")
|
|
}
|