Add allowlisted markdown indexing, read-only git plumbing via injected runners, and docker/readiness collection with path and loopback guards. Co-authored-by: Cursor <cursoragent@cursor.com>
112 lines
3.1 KiB
Go
112 lines
3.1 KiB
Go
package engineeringdashboard
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDocs_IndexAndBacklinks(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeDoc(t, root, ".doc/ops/CURRENT.md", "---\nstatus: in_progress\n---\n# Current\n\nSee [task](./short-term/task.md).\n")
|
|
writeDoc(t, root, ".doc/ops/short-term/task.md", "---\ntitle: Demo Task\nstatus: planned\nowner: unassigned\nmutex: demo\n---\n# Demo Task\n\nLink to [current](../CURRENT.md).\n")
|
|
writeDoc(t, root, ".doc/ops/short-term/orphan.md", "# Orphan\n\nNo links.\n")
|
|
// assets must be skipped
|
|
mustMkdir(t, filepath.Join(root, ".doc/test-reports/assets"))
|
|
if err := os.WriteFile(filepath.Join(root, ".doc/test-reports/assets/secret.md"), []byte("# secret"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
res := CollectDocs(root, now)
|
|
if res.Source.Status != SourceOK {
|
|
t.Fatalf("docs source: %+v", res.Source)
|
|
}
|
|
var foundAssets bool
|
|
var task *DocumentNode
|
|
for i := range res.Documents {
|
|
if strings.Contains(res.Documents[i].Path, "assets/") {
|
|
foundAssets = true
|
|
}
|
|
if strings.HasSuffix(res.Documents[i].Path, "short-term/task.md") {
|
|
task = &res.Documents[i]
|
|
}
|
|
}
|
|
if foundAssets {
|
|
t.Fatal("assets markdown must not be indexed as body")
|
|
}
|
|
if task == nil {
|
|
t.Fatal("task doc missing")
|
|
}
|
|
if len(task.LinksIn) == 0 {
|
|
t.Fatalf("expected backlink into task, got %+v", task)
|
|
}
|
|
var planned bool
|
|
for _, tk := range res.Tasks {
|
|
if strings.Contains(tk.Path, "task.md") {
|
|
if tk.DocStatus != DocStatusPlanned || tk.Claim.Claimed {
|
|
t.Fatalf("rdev-like task should stay planned/unclaimed: %+v", tk)
|
|
}
|
|
planned = true
|
|
}
|
|
}
|
|
if !planned {
|
|
t.Fatal("task not parsed into TaskState")
|
|
}
|
|
}
|
|
|
|
func TestDocs_BrokenLinkDoesNotFailIndex(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeDoc(t, root, ".doc/ops/note.md", "# Note\n\n[missing](./nope.md)\n")
|
|
res := CollectDocs(root, time.Now().UTC())
|
|
if res.Source.Status != SourceOK {
|
|
t.Fatalf("index should succeed: %+v", res.Source)
|
|
}
|
|
found := false
|
|
for _, d := range res.Documents {
|
|
if strings.HasSuffix(d.Path, "note.md") && len(d.BrokenLinks) > 0 {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("expected broken link recorded")
|
|
}
|
|
}
|
|
|
|
func TestPathSafe_RejectsTraversalAndSymlink(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeDoc(t, root, ".doc/ops/ok.md", "# ok\n")
|
|
if _, err := SafeDocPath(root, ".doc/../.env"); err == nil {
|
|
t.Fatal("expected traversal deny")
|
|
}
|
|
target := filepath.Join(root, ".doc/ops/ok.md")
|
|
link := filepath.Join(root, ".doc/ops/link.md")
|
|
if err := os.Symlink(target, link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := SafeDocPath(root, ".doc/ops/link.md"); err == nil {
|
|
t.Fatal("expected symlink deny")
|
|
}
|
|
if _, err := SafeDocPath(root, ".doc/ops/ok.md"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func writeDoc(t *testing.T, root, rel, body string) {
|
|
t.Helper()
|
|
abs := filepath.Join(root, filepath.FromSlash(rel))
|
|
mustMkdir(t, filepath.Dir(abs))
|
|
if err := os.WriteFile(abs, []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func mustMkdir(t *testing.T, dir string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|