package cc import ( "context" "errors" "testing" ) func withRunner(fn func(ctx context.Context, cwd string, args []string) ([]byte, []byte, error)) func() { orig := RunClaude RunClaude = fn return func() { RunClaude = orig } } func TestSpawnClaude_CleanJSON(t *testing.T) { restore := withRunner(func(_ context.Context, _ string, _ []string) ([]byte, []byte, error) { return []byte(`{"result":"ok","session_id":"abc123"}`), nil, nil }) defer restore() res, err := SpawnClaude(context.Background(), Opts{Prompt: "hello"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.SessionID != "abc123" { t.Errorf("SessionID = %q, want abc123", res.SessionID) } if res.ResultText != "ok" { t.Errorf("ResultText = %q, want ok", res.ResultText) } } func TestSpawnClaude_NDJSONTail(t *testing.T) { ndjson := `{"type":"progress","data":"thinking"} {"type":"progress","data":"still thinking"} {"result":"done","session_id":"sess99"}` restore := withRunner(func(_ context.Context, _ string, _ []string) ([]byte, []byte, error) { return []byte(ndjson), nil, nil }) defer restore() res, err := SpawnClaude(context.Background(), Opts{Prompt: "go"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.SessionID != "sess99" { t.Errorf("SessionID = %q, want sess99", res.SessionID) } if res.ResultText != "done" { t.Errorf("ResultText = %q, want done", res.ResultText) } } func TestSpawnClaude_NonZeroExit(t *testing.T) { restore := withRunner(func(_ context.Context, _ string, _ []string) ([]byte, []byte, error) { return nil, []byte("something went wrong"), errors.New("exit status 1") }) defer restore() _, err := SpawnClaude(context.Background(), Opts{Prompt: "hi"}) if err == nil { t.Fatal("expected error, got nil") } var ce *ClaudeError if !errors.As(err, &ce) { t.Errorf("expected ClaudeError, got %T: %v", err, err) } } func TestSpawnClaude_Timeout(t *testing.T) { restore := withRunner(func(ctx context.Context, _ string, _ []string) ([]byte, []byte, error) { // Respect context cancellation — as the real runner would. <-ctx.Done() return nil, nil, ctx.Err() }) defer restore() _, err := SpawnClaude(context.Background(), Opts{Prompt: "slow", TimeoutMs: 1}) if err == nil { t.Fatal("expected timeout error, got nil") } var te *ClaudeTimeoutError if !errors.As(err, &te) { t.Errorf("expected ClaudeTimeoutError, got %T: %v", err, err) } }