package ci import ( "context" "encoding/binary" "net" "net/http" "net/http/httptest" "path/filepath" "testing" "hearthforge/internal/config" ) // muxFrame builds one Docker multiplexed stream frame. func muxFrame(text string) []byte { hdr := make([]byte, 8) hdr[0] = 1 binary.BigEndian.PutUint32(hdr[4:], uint32(len(text))) return append(hdr, text...) } // newMockEngine serves a fake engine on a unix socket and returns a Runner // pointed at it. func newMockEngine(t *testing.T, h http.Handler) *Runner { t.Helper() sock := filepath.Join(t.TempDir(), "engine.sock") ln, err := net.Listen("unix", sock) if err != nil { t.Fatalf("listen: %v", err) } srv := httptest.Server{Listener: ln, Config: &http.Server{Handler: h}} srv.Start() t.Cleanup(srv.Close) return New(&config.Config{CIDockerSocket: sock}, nil) } func TestExecReadsLogAndExitCode(t *testing.T) { r := newMockEngine(t, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/v1.47/containers/abc/exec": w.Write([]byte(`{"Id":"exec1"}`)) case "/v1.47/exec/exec1/start": w.Write(muxFrame("hello ")) w.Write(muxFrame("world\n")) case "/v1.47/exec/exec1/json": w.Write([]byte(`{"ExitCode":3}`)) default: t.Errorf("unexpected request %s", req.URL.Path) } })) res, err := r.exec(context.Background(), "abc", []string{"true"}, "/ci", nil, nil) if err != nil { t.Fatalf("exec: %v", err) } if res.log != "hello world\n" || res.exitCode != 3 { t.Fatalf("got %q exit %d", res.log, res.exitCode) } } func TestSocketMissingIsReported(t *testing.T) { r := New(&config.Config{CIDockerSocket: "/nonexistent/engine.sock"}, nil) _, err := r.exec(context.Background(), "abc", []string{"true"}, "", nil, nil) if err == nil || err.Error() != errNoSocket.Error() { t.Fatalf("got %v", err) } }