agent-orchestrator/backend/internal/cli/preview_test.go

194 lines
5.7 KiB
Go

package cli
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// previewCapture records the request body and path the CLI hit, plus whether
// the daemon was contacted at all.
type previewCapture struct {
body string
path string
method string
called bool
}
// previewServer wires an httptest server expecting POST or DELETE on
// /api/v1/sessions/{id}/preview and captures what the CLI sent.
func previewServer(t *testing.T, status int, respBody string) (*httptest.Server, *previewCapture) {
t.Helper()
capture := &previewCapture{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost && r.Method != http.MethodDelete {
http.NotFound(w, r)
return
}
if !strings.HasPrefix(r.URL.Path, "/api/v1/sessions/") || !strings.HasSuffix(r.URL.Path, "/preview") {
http.NotFound(w, r)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
t.Fatalf("read body: %v", err)
}
capture.called = true
capture.body = string(body)
capture.path = r.URL.Path
capture.method = r.Method
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, _ = io.WriteString(w, respBody)
}))
t.Cleanup(srv.Close)
return srv, capture
}
func TestPreview_WithURLArg(t *testing.T) {
t.Setenv("AO_SESSION_ID", "aa-47")
cfg := setConfigEnv(t)
srv, capture := previewServer(t, http.StatusOK, `{"ok":true}`)
writeRunFileFor(t, cfg, srv)
_, errOut, err := executeCLI(t, Deps{
ProcessAlive: func(int) bool { return true },
}, "preview", "http://localhost:5173")
if err != nil {
t.Fatalf("unexpected error: %v\nstderr=%s", err, errOut)
}
if capture.path != "/api/v1/sessions/aa-47/preview" {
t.Errorf("path = %q, want /api/v1/sessions/aa-47/preview", capture.path)
}
var req struct {
Url string `json:"url"`
}
if err := json.Unmarshal([]byte(capture.body), &req); err != nil {
t.Fatalf("decode body: %v\nbody=%s", err, capture.body)
}
if req.Url != "http://localhost:5173" {
t.Errorf("captured url = %q, want %q", req.Url, "http://localhost:5173")
}
}
func TestPreview_NoArgPostsEmptyURL(t *testing.T) {
t.Setenv("AO_SESSION_ID", "aa-47")
cfg := setConfigEnv(t)
srv, capture := previewServer(t, http.StatusOK, `{"ok":true}`)
writeRunFileFor(t, cfg, srv)
_, errOut, err := executeCLI(t, Deps{
ProcessAlive: func(int) bool { return true },
}, "preview")
if err != nil {
t.Fatalf("unexpected error: %v\nstderr=%s", err, errOut)
}
if capture.body != `{"url":""}` {
t.Errorf("captured body = %q, want %q", capture.body, `{"url":""}`)
}
}
func TestPreviewClear_DeletesSessionPreview(t *testing.T) {
t.Setenv("AO_SESSION_ID", "aa-47")
cfg := setConfigEnv(t)
srv, capture := previewServer(t, http.StatusOK, `{"ok":true}`)
writeRunFileFor(t, cfg, srv)
_, errOut, err := executeCLI(t, Deps{
ProcessAlive: func(int) bool { return true },
}, "preview", "clear")
if err != nil {
t.Fatalf("unexpected error: %v\nstderr=%s", err, errOut)
}
if capture.method != http.MethodDelete {
t.Errorf("method = %q, want DELETE", capture.method)
}
if capture.path != "/api/v1/sessions/aa-47/preview" {
t.Errorf("path = %q, want /api/v1/sessions/aa-47/preview", capture.path)
}
}
func TestPreviewClear_MissingSessionIDIsUsageError(t *testing.T) {
t.Setenv("AO_SESSION_ID", "")
cfg := setConfigEnv(t)
srv, capture := previewServer(t, http.StatusOK, `{"ok":true}`)
writeRunFileFor(t, cfg, srv)
_, _, err := executeCLI(t, Deps{
ProcessAlive: func(int) bool { return true },
}, "preview", "clear")
if err == nil {
t.Fatal("expected usage error when AO_SESSION_ID is unset")
}
if got := ExitCode(err); got != 2 {
t.Fatalf("exit code = %d, want 2", got)
}
if capture.called {
t.Fatal("daemon should not be contacted when AO_SESSION_ID is unset")
}
}
func TestPreview_MissingSessionIDIsUsageError(t *testing.T) {
t.Setenv("AO_SESSION_ID", "")
cfg := setConfigEnv(t)
srv, capture := previewServer(t, http.StatusOK, `{"ok":true}`)
writeRunFileFor(t, cfg, srv)
_, _, err := executeCLI(t, Deps{
ProcessAlive: func(int) bool { return true },
}, "preview", "http://localhost:5173")
if err == nil {
t.Fatal("expected usage error when AO_SESSION_ID is unset")
}
if got := ExitCode(err); got != 2 {
t.Fatalf("exit code = %d, want 2", got)
}
if !strings.Contains(err.Error(), "AO_SESSION_ID is not set") {
t.Fatalf("error missing usage message: %v", err)
}
if capture.called {
t.Fatal("daemon should not be contacted when AO_SESSION_ID is unset")
}
}
func TestPreview_HelpIncludesExamples(t *testing.T) {
out, _, err := executeCLI(t, Deps{}, "preview", "--help")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Examples section present.
if !strings.Contains(out, "EXAMPLES") && !strings.Contains(out, "Examples") {
t.Errorf("help output missing Examples section:\n%s", out)
}
// file:// URL example (not a relative path).
if !strings.Contains(out, "file://$(pwd)/index.html") {
t.Errorf("help output missing file:// example:\n%s", out)
}
if strings.Contains(out, "./dist/index.html") {
t.Errorf("help output still references relative ./dist/index.html:\n%s", out)
}
}
func TestPreview_BlankSessionIDIsUsageError(t *testing.T) {
t.Setenv("AO_SESSION_ID", " \t ")
cfg := setConfigEnv(t)
srv, capture := previewServer(t, http.StatusOK, `{"ok":true}`)
writeRunFileFor(t, cfg, srv)
_, _, err := executeCLI(t, Deps{
ProcessAlive: func(int) bool { return true },
}, "preview")
if err == nil {
t.Fatal("expected usage error when AO_SESSION_ID is blank")
}
if got := ExitCode(err); got != 2 {
t.Fatalf("exit code = %d, want 2", got)
}
if capture.called {
t.Fatal("daemon should not be contacted when AO_SESSION_ID is blank")
}
}