132 lines
3.9 KiB
Go
132 lines
3.9 KiB
Go
package agent
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestMatchDeliverableRel_Aliases(t *testing.T) {
|
|
workDir := filepath.Join(string(filepath.Separator), "app", "data", "workspaces", "bizdev", "prelim_x", "tasks", "01_draft")
|
|
abs := filepath.Join(workDir, "output.md")
|
|
cases := []struct {
|
|
rel string
|
|
want bool
|
|
}{
|
|
{"output.md", true},
|
|
{"./output.md", true},
|
|
{abs, true},
|
|
{"tasks/01_draft/output.md", true},
|
|
{"execution.log", false},
|
|
{"../output.md", false},
|
|
{"../../output.md", false},
|
|
{"/etc/passwd", false},
|
|
{"tasks/02_review/output.md", false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := MatchDeliverableRel(workDir, tc.rel, "output.md"); got != tc.want {
|
|
t.Errorf("MatchDeliverableRel(%q) = %v, want %v", tc.rel, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMaterializeTargets_MapsAbsoluteAndNested(t *testing.T) {
|
|
workDir := filepath.Join(t.TempDir(), "tasks", "01_draft")
|
|
if err := os.MkdirAll(workDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body := []byte("# 系统初步设计方案\n\n真实 runner 产物\n")
|
|
incoming := map[string][]byte{
|
|
filepath.Join(workDir, "output.md"): body,
|
|
}
|
|
got, err := MaterializeTargets(workDir, []string{"output.md"}, incoming)
|
|
if err != nil {
|
|
t.Fatalf("absolute rel_path must materialize: %v", err)
|
|
}
|
|
if string(got["output.md"]) != string(body) {
|
|
t.Fatalf("canonical patch missing")
|
|
}
|
|
onDisk, err := os.ReadFile(filepath.Join(workDir, "output.md"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(onDisk) != string(body) {
|
|
t.Fatalf("disk output.md mismatch")
|
|
}
|
|
|
|
nestedDir := filepath.Join(t.TempDir(), "ws", "tasks", "01_draft")
|
|
if err := os.MkdirAll(nestedDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := MaterializeTargets(nestedDir, []string{"output.md"}, map[string][]byte{
|
|
"tasks/01_draft/output.md": []byte("# nested alias\n"),
|
|
}); err != nil {
|
|
t.Fatalf("duplicated step prefix must map: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMaterializeTargets_MissingAndEmptyFail(t *testing.T) {
|
|
workDir := t.TempDir()
|
|
if _, err := MaterializeTargets(workDir, []string{"output.md"}, map[string][]byte{
|
|
"execution.log": []byte("Outcome: SUCCESS\n"),
|
|
}); err == nil || !strings.Contains(err.Error(), "missing target deliverable output.md") {
|
|
t.Fatalf("must fail closed without inventing output.md, got %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(workDir, "output.md")); err == nil {
|
|
t.Fatal("must not write a stub output.md")
|
|
}
|
|
if _, err := MaterializeTargets(workDir, []string{"output.md"}, map[string][]byte{
|
|
"output.md": []byte(" \n"),
|
|
}); err == nil {
|
|
t.Fatal("whitespace-only body must not count as a deliverable")
|
|
}
|
|
}
|
|
|
|
func TestMaterializeTargets_EmptyPatchKeepsExistingFile(t *testing.T) {
|
|
workDir := t.TempDir()
|
|
body := []byte("# 调研报告\n已写在盘上的正文\n")
|
|
path := filepath.Join(workDir, "output.md")
|
|
if err := os.WriteFile(path, body, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := MaterializeTargets(workDir, []string{"output.md"}, map[string][]byte{
|
|
"output.md": nil,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got["output.md"]) != string(body) {
|
|
t.Fatalf("collected %q", got["output.md"])
|
|
}
|
|
disk, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(disk) != string(body) {
|
|
t.Fatalf("disk overwritten with %q", disk)
|
|
}
|
|
}
|
|
|
|
func TestMaterializeTargets_RejectsSymlinkEscape(t *testing.T) {
|
|
root := t.TempDir()
|
|
outside := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(outside, "secret.md"), []byte("leak"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
link := filepath.Join(root, "out")
|
|
if err := os.Symlink(outside, link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err := MaterializeTargets(root, []string{"out/secret.md"}, map[string][]byte{
|
|
"out/secret.md": []byte("overwrite"),
|
|
})
|
|
if err == nil {
|
|
t.Fatal("symlink escape must fail closed")
|
|
}
|
|
got, _ := os.ReadFile(filepath.Join(outside, "secret.md"))
|
|
if string(got) != "leak" {
|
|
t.Fatalf("must not write through symlink, got %q", got)
|
|
}
|
|
}
|