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>
118 lines
3.8 KiB
Go
118 lines
3.8 KiB
Go
package engineeringdashboard
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRuntime_ReadyOKAndEventsRestarting(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
dockerLines := []map[string]any{
|
|
{"Names": "shuimu-agent-main", "Image": "agent:main", "Status": "Up (healthy)", "Labels": "com.docker.compose.project=agent-main,com.docker.compose.service=app"},
|
|
{"Names": "shuimu-agent-events-main", "Image": "events:main", "Status": "Restarting (1) 2 seconds ago", "Labels": "com.docker.compose.project=agent-main,com.docker.compose.service=events"},
|
|
}
|
|
var b strings.Builder
|
|
for _, row := range dockerLines {
|
|
raw, _ := json.Marshal(row)
|
|
b.Write(raw)
|
|
b.WriteByte('\n')
|
|
}
|
|
fake := &FakeRunner{Map: map[string]FakeResult{
|
|
"docker ps -a --format {{json .}}": {Stdout: b.String()},
|
|
}}
|
|
|
|
// Override ready endpoints via temporary server on allowlisted pattern — use real client against loopback.
|
|
// CollectRuntime uses fixed ports; for unit test we only validate docker facet path by calling parse helpers.
|
|
cv, env, ok := parseDockerLine(string(mustJSON(dockerLines[1])))
|
|
if !ok || env != "main" || cv.Status == "" {
|
|
t.Fatalf("parse failed: %+v %s", cv, env)
|
|
}
|
|
envState := EnvironmentState{
|
|
Name: "main",
|
|
Containers: []ContainerView{
|
|
mustParseCV(dockerLines[0]),
|
|
mustParseCV(dockerLines[1]),
|
|
},
|
|
Readiness: "ok",
|
|
}
|
|
envState.Facets.ContainerStatus = summarizeContainers(envState.Containers)
|
|
envState.Facets.AppReadiness = "ok"
|
|
envState.Facets.ServicesOK = summarizeServices(envState.Containers, "ok")
|
|
if envState.Facets.ContainerStatus != "partial" || envState.Facets.ServicesOK != "partial" {
|
|
t.Fatalf("expected partial facets: %+v", envState.Facets)
|
|
}
|
|
_ = fake
|
|
_ = now
|
|
}
|
|
|
|
func TestRuntime_RedirectOffAllowlistDenied(t *testing.T) {
|
|
if err := assertLoopbackReadyURL("http://example.com/ready"); err == nil {
|
|
t.Fatal("expected deny")
|
|
}
|
|
if err := assertLoopbackReadyURL("http://127.0.0.1:9999/ready"); err == nil {
|
|
t.Fatal("expected port deny")
|
|
}
|
|
if err := assertLoopbackReadyURL("http://127.0.0.1:8080/ready"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestRuntime_DockerUnavailableStillReturnsEnvs(t *testing.T) {
|
|
fake := &FakeRunner{Map: map[string]FakeResult{
|
|
"docker ps -a --format {{json .}}": {Err: errorsNew("docker missing")},
|
|
}}
|
|
// Avoid hitting real ready ports: still OK if degraded.
|
|
res := CollectRuntime(context.Background(), t.TempDir(), fake, time.Now().UTC())
|
|
if len(res.Environments) != 4 {
|
|
t.Fatalf("expected 4 envs: %+v", res.Environments)
|
|
}
|
|
if res.Source.Status != SourceDegraded && res.Source.Status != SourceOK {
|
|
t.Fatalf("unexpected source: %+v", res.Source)
|
|
}
|
|
}
|
|
|
|
func TestRuntime_NoEnvInOutput(t *testing.T) {
|
|
line := `{"Names":"x","Image":"i","Status":"Up","Labels":"com.docker.compose.project=agent-dev,com.docker.compose.service=app","Env":["SECRET=1"]}`
|
|
cv, _, ok := parseDockerLine(line)
|
|
if !ok {
|
|
t.Fatal("parse failed")
|
|
}
|
|
raw, _ := json.Marshal(cv)
|
|
if strings.Contains(string(raw), "SECRET") || strings.Contains(string(raw), "Env") {
|
|
t.Fatalf("env leaked: %s", raw)
|
|
}
|
|
}
|
|
|
|
func TestReadyHTTP_UsesLoopbackServer(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(200)
|
|
_, _ = w.Write([]byte(`{"schema_version":48}`))
|
|
}))
|
|
defer srv.Close()
|
|
// server URL is 127.0.0.1:random — not allowlisted port, assertLoopbackReadyURL should fail
|
|
if err := assertLoopbackReadyURL(srv.URL + "/ready"); err == nil {
|
|
t.Fatal("random port must be denied by allowlist")
|
|
}
|
|
}
|
|
|
|
func mustJSON(v any) []byte {
|
|
b, _ := json.Marshal(v)
|
|
return b
|
|
}
|
|
|
|
func mustParseCV(row map[string]any) ContainerView {
|
|
cv, _, _ := parseDockerLine(string(mustJSON(row)))
|
|
return cv
|
|
}
|
|
|
|
func errorsNew(s string) error { return &simpleErr{s} }
|
|
|
|
type simpleErr struct{ s string }
|
|
|
|
func (e *simpleErr) Error() string { return e.s }
|