nub/internal/ctxasm/assemble_test.go
Tom a97013d876 initial commit
- v 0.1.0 siehe CHANGELOG.md
2026-07-25 11:08:01 +02:00

160 lines
4.2 KiB
Go

package ctxasm
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"nub/internal/llm"
)
func writeFile(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
func TestAssemble_WalkUp_OrdersRepoRootBeforeCwd(t *testing.T) {
repoRoot := t.TempDir()
sub := filepath.Join(repoRoot, "sub", "deeper")
if err := os.MkdirAll(sub, 0o755); err != nil {
t.Fatal(err)
}
writeFile(t, filepath.Join(repoRoot, "AGENTS.md"), "root instructions")
writeFile(t, filepath.Join(sub, "AGENTS.md"), "deeper instructions")
blocks, meta, err := Assemble(Options{
RepoRoot: repoRoot, Cwd: sub,
Files: []string{"AGENTS.md"}, WalkUp: true, MaxTokens: 20000,
})
if err != nil {
t.Fatal(err)
}
if len(meta.Files) != 2 {
t.Fatalf("loaded %d files, want 2", len(meta.Files))
}
if meta.Files[0].Path != "AGENTS.md" {
t.Errorf("first file = %q, want repo-root AGENTS.md first", meta.Files[0].Path)
}
if meta.Files[1].Path != filepath.Join("sub", "deeper", "AGENTS.md") {
t.Errorf("second file = %q, want the deeper one last", meta.Files[1].Path)
}
joined := joinBlockTexts(blocks)
rootIdx := strings.Index(joined, "root instructions")
deeperIdx := strings.Index(joined, "deeper instructions")
if rootIdx == -1 || deeperIdx == -1 || rootIdx > deeperIdx {
t.Errorf("expected root instructions before deeper instructions in assembled text")
}
}
func TestAssemble_LastBlockHasCacheMark(t *testing.T) {
repoRoot := t.TempDir()
blocks, _, err := Assemble(Options{RepoRoot: repoRoot, Cwd: repoRoot, MaxTokens: 20000})
if err != nil {
t.Fatal(err)
}
for i, b := range blocks {
want := i == len(blocks)-1
if b.CacheMark != want {
t.Errorf("block %d CacheMark = %v, want %v", i, b.CacheMark, want)
}
}
}
func TestAssemble_BudgetExceeded_TruncatesAndWarns(t *testing.T) {
repoRoot := t.TempDir()
big := strings.Repeat("word ", 2000)
writeFile(t, filepath.Join(repoRoot, "AGENTS.md"), big)
_, meta, err := Assemble(Options{
RepoRoot: repoRoot, Cwd: repoRoot,
Files: []string{"AGENTS.md"}, WalkUp: false, MaxTokens: 100,
})
if err != nil {
t.Fatal(err)
}
if len(meta.Warnings) == 0 {
t.Fatal("expected a budget warning")
}
if len(meta.Files) != 1 || !meta.Files[0].Truncated {
t.Errorf("expected AGENTS.md to be marked truncated, got: %+v", meta.Files)
}
}
func TestAssemble_MissingFilesAreSkippedSilently(t *testing.T) {
repoRoot := t.TempDir()
_, meta, err := Assemble(Options{
RepoRoot: repoRoot, Cwd: repoRoot,
Files: []string{"DOES_NOT_EXIST.md"}, WalkUp: false, MaxTokens: 20000,
})
if err != nil {
t.Fatal(err)
}
if len(meta.Files) != 0 {
t.Errorf("expected no files loaded, got %+v", meta.Files)
}
}
func TestAssemble_WarnsOnStaleRepoMap(t *testing.T) {
repoRoot := t.TempDir()
runGit(t, repoRoot, "init")
writeFile(t, filepath.Join(repoRoot, "f.txt"), "x")
runGit(t, repoRoot, "add", "-A")
runGit(t, repoRoot, "-c", "user.email=t@t.com", "-c", "user.name=t", "commit", "-m", "init")
content, err := GenerateRepoMap(repoRoot)
if err != nil {
t.Fatal(err)
}
writeFile(t, filepath.Join(repoRoot, "REPOMAP.md"), content)
// Neuer Commit nach Repomap-Generierung -> Repomap ist jetzt stale.
writeFile(t, filepath.Join(repoRoot, "f.txt"), "y")
runGit(t, repoRoot, "add", "-A")
runGit(t, repoRoot, "-c", "user.email=t@t.com", "-c", "user.name=t", "commit", "-m", "change")
_, meta, err := Assemble(Options{
RepoRoot: repoRoot, Cwd: repoRoot,
Files: []string{"REPOMAP.md"}, WalkUp: false, MaxTokens: 20000,
})
if err != nil {
t.Fatal(err)
}
found := false
for _, w := range meta.Warnings {
if strings.Contains(w, "REPOMAP.md") {
found = true
}
}
if !found {
t.Errorf("expected a staleness warning, got: %v", meta.Warnings)
}
}
func runGit(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}
func joinBlockTexts(blocks []llm.Block) string {
var b strings.Builder
for _, blk := range blocks {
b.WriteString(blk.Text)
b.WriteString("\n")
}
return b.String()
}