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 }