package launch

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/ollama/ollama/cmd/config"
	"github.com/spf13/cobra"
)

func captureStderr(t *testing.T, fn func()) string {
	t.Helper()

	oldStderr := os.Stderr
	r, w, err := os.Pipe()
	if err != nil {
		t.Fatalf("failed to create stderr pipe: %v", err)
	}
	os.Stderr = w
	defer func() {
		os.Stderr = oldStderr
	}()

	done := make(chan string, 1)
	go func() {
		var buf bytes.Buffer
		_, _ = io.Copy(&buf, r)
		done <- buf.String()
	}()

	fn()

	_ = w.Close()
	return <-done
}

func TestLaunchCmd(t *testing.T) {
	mockCheck := func(cmd *cobra.Command, args []string) error {
		return nil
	}
	mockTUI := func(cmd *cobra.Command) {}
	cmd := LaunchCmd(mockCheck, mockTUI)

	t.Run("command structure", func(t *testing.T) {
		if cmd.Use != "launch [INTEGRATION] [-- [EXTRA_ARGS...]]" {
			t.Errorf("Use = %q, want %q", cmd.Use, "launch [INTEGRATION] [-- [EXTRA_ARGS...]]")
		}
		if cmd.Short == "" {
			t.Error("Short description should not be empty")
		}
		if cmd.Long == "" {
			t.Error("Long description should not be empty")
		}
		if !strings.Contains(cmd.Long, "hermes") {
			t.Error("Long description should mention hermes")
		}
		if !strings.Contains(cmd.Long, "kimi") {
			t.Error("Long description should mention kimi")
		}
	})

	t.Run("flags exist", func(t *testing.T) {
		if cmd.Flags().Lookup("model") == nil {
			t.Error("--model flag should exist")
		}
		if cmd.Flags().Lookup("config") == nil {
			t.Error("--config flag should exist")
		}
		if cmd.Flags().Lookup("restore") == nil {
			t.Error("--restore flag should exist")
		}
		if cmd.Flags().Lookup("yes") == nil {
			t.Error("--yes flag should exist")
		}
	})

	t.Run("PreRunE is set", func(t *testing.T) {
		if cmd.PreRunE == nil {
			t.Error("PreRunE should be set to checkServerHeartbeat")
		}
	})
}

func TestLaunchCmdTUICallback(t *testing.T) {
	mockCheck := func(cmd *cobra.Command, args []string) error {
		return nil
	}

	t.Run("no args calls TUI", func(t *testing.T) {
		tuiCalled := false
		mockTUI := func(cmd *cobra.Command) {
			tuiCalled = true
		}

		cmd := LaunchCmd(mockCheck, mockTUI)
		cmd.SetArgs([]string{})
		_ = cmd.Execute()

		if !tuiCalled {
			t.Error("TUI callback should be called when no args provided")
		}
	})

	t.Run("integration arg bypasses TUI", func(t *testing.T) {
		srv := httptest.NewServer(http.NotFoundHandler())
		defer srv.Close()
		t.Setenv("OLLAMA_HOST", srv.URL)

		tuiCalled := false
		mockTUI := func(cmd *cobra.Command) {
			tuiCalled = true
		}

		cmd := LaunchCmd(mockCheck, mockTUI)
		cmd.SetArgs([]string{"claude"})
		_ = cmd.Execute()

		if tuiCalled {
			t.Error("TUI callback should NOT be called when integration arg provided")
		}
	})

	t.Run("--model flag without integration returns error", func(t *testing.T) {
		tuiCalled := false
		mockTUI := func(cmd *cobra.Command) {
			tuiCalled = true
		}

		cmd := LaunchCmd(mockCheck, mockTUI)
		cmd.SetArgs([]string{"--model", "test-model"})
		err := cmd.Execute()

		if err == nil {
			t.Fatal("expected --model without an integration to fail")
		}
		if !strings.Contains(err.Error(), "require an integration name") {
			t.Fatalf("expected integration-name guidance, got %v", err)
		}
		if tuiCalled {
			t.Error("TUI callback should NOT be called when --model is provided without an integration")
		}
	})

	t.Run("--config flag without integration returns error", func(t *testing.T) {
		tuiCalled := false
		mockTUI := func(cmd *cobra.Command) {
			tuiCalled = true
		}

		cmd := LaunchCmd(mockCheck, mockTUI)
		cmd.SetArgs([]string{"--config"})
		err := cmd.Execute()

		if err == nil {
			t.Fatal("expected --config without an integration to fail")
		}
		if !strings.Contains(err.Error(), "require an integration name") {
			t.Fatalf("expected integration-name guidance, got %v", err)
		}
		if tuiCalled {
			t.Error("TUI callback should NOT be called when --config is provided without an integration")
		}
	})

	t.Run("--yes flag without integration returns error", func(t *testing.T) {
		tuiCalled := false
		mockTUI := func(cmd *cobra.Command) {
			tuiCalled = true
		}

		cmd := LaunchCmd(mockCheck, mockTUI)
		cmd.SetArgs([]string{"--yes"})
		err := cmd.Execute()

		if err == nil {
			t.Fatal("expected --yes without an integration to fail")
		}
		if !strings.Contains(err.Error(), "require an integration name") {
			t.Fatalf("expected integration-name guidance, got %v", err)
		}
		if tuiCalled {
			t.Error("TUI callback should NOT be called when --yes is provided without an integration")
		}
	})

	t.Run("extra args without integration return error", func(t *testing.T) {
		tuiCalled := false
		mockTUI := func(cmd *cobra.Command) {
			tuiCalled = true
		}

		cmd := LaunchCmd(mockCheck, mockTUI)
		cmd.SetArgs([]string{"--model", "test-model", "--", "--sandbox", "workspace-write"})
		err := cmd.Execute()

		if err == nil {
			t.Fatal("expected flags and extra args without an integration to fail")
		}
		if !strings.Contains(err.Error(), "require an integration name") {
			t.Fatalf("expected integration-name guidance, got %v", err)
		}
		if tuiCalled {
			t.Error("TUI callback should NOT be called when flags or extra args are provided without an integration")
		}
	})

	t.Run("--restore flag without integration returns error", func(t *testing.T) {
		tuiCalled := false
		mockTUI := func(cmd *cobra.Command) {
			tuiCalled = true
		}

		cmd := LaunchCmd(mockCheck, mockTUI)
		cmd.SetArgs([]string{"--restore"})
		err := cmd.Execute()

		if err == nil {
			t.Fatal("expected --restore without an integration to fail")
		}
		if !strings.Contains(err.Error(), "require an integration name") {
			t.Fatalf("expected integration-name guidance, got %v", err)
		}
		if tuiCalled {
			t.Error("TUI callback should NOT be called when --restore is provided without an integration")
		}
	})
}

func TestLaunchCmdClaudeDesktopLaunchReturnsUnsupported(t *testing.T) {
	for _, name := range []string{"claude-desktop", "claude-app"} {
		t.Run(name, func(t *testing.T) {
			cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error {
				t.Fatal("heartbeat check should not run before Claude Desktop unsupported error")
				return nil
			}, func(cmd *cobra.Command) {
				t.Fatal("TUI callback should not run for direct integration launch")
			})
			cmd.SetArgs([]string{name})

			err := cmd.Execute()
			if err == nil {
				t.Fatal("expected Claude Desktop launch command to fail")
			}
			if !strings.Contains(err.Error(), "Claude Desktop is no longer supported") {
				t.Fatalf("expected unsupported guidance, got %v", err)
			}
			if !strings.Contains(err.Error(), "ollama launch claude-desktop --restore") {
				t.Fatalf("expected restore guidance, got %v", err)
			}
		})
	}
}

func TestLaunchCmdNilHeartbeat(t *testing.T) {
	cmd := LaunchCmd(nil, nil)
	if cmd == nil {
		t.Fatal("LaunchCmd returned nil")
	}
	if cmd.PreRunE != nil {
		t.Log("Note: PreRunE is set even when nil is passed (acceptable)")
	}
}

func TestLaunchCmdModelFlagFiltersDisabledCloudFromSavedConfig(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)

	if err := config.SaveIntegration("stubeditor", []string{"glm-5:cloud"}); err != nil {
		t.Fatalf("failed to seed saved config: %v", err)
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/status":
			fmt.Fprintf(w, `{"cloud":{"disabled":true,"source":"config"}}`)
		case "/api/show":
			fmt.Fprintf(w, `{"model":"sample-model"}`)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherEditorRunner{}
	restore := OverrideIntegration("stubeditor", stub)
	defer restore()

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubeditor", "--model", "sample-model"})
	if err := cmd.Execute(); err != nil {
		t.Fatalf("launch command failed: %v", err)
	}

	saved, err := config.LoadIntegration("stubeditor")
	if err != nil {
		t.Fatalf("failed to reload integration config: %v", err)
	}
	if diff := cmp.Diff([]string{"sample-model"}, saved.Models); diff != "" {
		t.Fatalf("saved models mismatch (-want +got):\n%s", diff)
	}
	if diff := cmp.Diff([][]string{{"sample-model"}}, stub.edited); diff != "" {
		t.Fatalf("editor models mismatch (-want +got):\n%s", diff)
	}
	if stub.ranModel != "sample-model" {
		t.Fatalf("expected launch to run with sample-model, got %q", stub.ranModel)
	}
}

func TestLaunchCmdModelFlagClearsDisabledCloudOverride(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/status":
			fmt.Fprintf(w, `{"cloud":{"disabled":true,"source":"config"}}`)
		case "/api/experimental/model-recommendations":
			fmt.Fprint(w, `{"recommendations":[]}`)
		case "/api/tags":
			fmt.Fprint(w, `{"models":[{"name":"sample-model"}]}`)
		case "/api/show":
			fmt.Fprint(w, `{"model":"sample-model"}`)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherSingleRunner{}
	restore := OverrideIntegration("stubapp", stub)
	defer restore()

	oldSelector := DefaultSingleSelector
	defer func() { DefaultSingleSelector = oldSelector }()

	var selectorCalls int
	var gotCurrent string
	DefaultSingleSelector = func(title string, items []SelectionItem, current string) (string, error) {
		selectorCalls++
		gotCurrent = current
		return "sample-model", nil
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubapp", "--model", "glm-5:cloud"})
	stderr := captureStderr(t, func() {
		if err := cmd.Execute(); err != nil {
			t.Fatalf("launch command failed: %v", err)
		}
	})

	if selectorCalls != 1 {
		t.Fatalf("expected disabled cloud override to fall back to selector, got %d calls", selectorCalls)
	}
	if gotCurrent != "" {
		t.Fatalf("expected disabled override to be cleared before selection, got current %q", gotCurrent)
	}
	if stub.ranModel != "sample-model" {
		t.Fatalf("expected launch to run with replacement local model, got %q", stub.ranModel)
	}
	if !strings.Contains(stderr, "Warning: ignoring --model glm-5:cloud because cloud is disabled") {
		t.Fatalf("expected disabled-cloud warning, got stderr: %q", stderr)
	}

	saved, err := config.LoadIntegration("stubapp")
	if err != nil {
		t.Fatalf("failed to reload integration config: %v", err)
	}
	if diff := cmp.Diff([]string{"sample-model"}, saved.Models); diff != "" {
		t.Fatalf("saved models mismatch (-want +got):\n%s", diff)
	}
}

func TestLaunchCmdAutodiscoveryDefaultLaunchDoesNotForceConfigure(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)
	withInteractiveSession(t, true)
	withLauncherHooks(t)

	runner := &launcherManagedAutodiscoveryRunner{
		autodiscoveryConfigured: true,
	}
	restore := OverrideIntegration("stubauto", runner)
	defer restore()

	if err := config.SaveIntegration("stubauto", []string{"Ollama Cloud"}); err != nil {
		t.Fatalf("failed to save managed integration config: %v", err)
	}
	if err := config.MarkIntegrationOnboarded("stubauto"); err != nil {
		t.Fatalf("failed to mark integration onboarded: %v", err)
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {
		t.Fatal("TUI callback should not run for direct integration launch")
	})
	cmd.SetArgs([]string{"stubauto"})
	if err := cmd.Execute(); err != nil {
		t.Fatalf("launch command failed: %v", err)
	}

	if runner.autodiscoveryConfigures != 0 {
		t.Fatalf("expected default autodiscovery launch to reuse existing config, got %d configures", runner.autodiscoveryConfigures)
	}
	if runner.ranModel != "Ollama Cloud" {
		t.Fatalf("expected launch to run autodiscovery label, got %q", runner.ranModel)
	}
}

func TestLaunchCmdYes_AutoConfirmsLaunchPromptPath(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)
	withLauncherHooks(t)
	withInteractiveSession(t, false)

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/show":
			fmt.Fprint(w, `{"model":"sample-model"}`)
		case "/api/status":
			w.WriteHeader(http.StatusNotFound)
			fmt.Fprint(w, `{"error":"not found"}`)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherEditorRunner{paths: []string{"/tmp/stubeditor.json"}}
	restore := OverrideIntegration("stubeditor", stub)
	defer restore()

	DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
		t.Fatalf("unexpected prompt with --yes: %q", prompt)
		return false, nil
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubeditor", "--model", "sample-model", "--yes"})
	if err := cmd.Execute(); err != nil {
		t.Fatalf("launch command with --yes failed: %v", err)
	}

	if diff := cmp.Diff([][]string{{"sample-model"}}, stub.edited); diff != "" {
		t.Fatalf("editor models mismatch (-want +got):\n%s", diff)
	}
	if stub.ranModel != "sample-model" {
		t.Fatalf("expected launch to run with sample-model, got %q", stub.ranModel)
	}
}

func TestLaunchCmdHeadlessWithYes_AutoPullsMissingLocalModel(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)
	withLauncherHooks(t)
	withInteractiveSession(t, false)

	var pullCalled bool
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/show":
			w.WriteHeader(http.StatusNotFound)
			fmt.Fprint(w, `{"error":"model not found"}`)
		case "/api/pull":
			pullCalled = true
			w.WriteHeader(http.StatusOK)
			fmt.Fprint(w, `{"status":"success"}`)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherSingleRunner{}
	restore := OverrideIntegration("stubapp", stub)
	defer restore()

	DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
		t.Fatalf("unexpected prompt with --yes in headless autopull path: %q", prompt)
		return false, nil
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubapp", "--model", "missing-model", "--yes"})
	if err := cmd.Execute(); err != nil {
		t.Fatalf("launch command with --yes failed: %v", err)
	}

	if !pullCalled {
		t.Fatal("expected missing local model to be auto-pulled with --yes in headless mode")
	}
	if stub.ranModel != "missing-model" {
		t.Fatalf("expected launch to run with pulled model, got %q", stub.ranModel)
	}
}

func TestLaunchCmdHeadlessWithoutYes_AllowsConfiguredLaunch(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)
	withLauncherHooks(t)
	withInteractiveSession(t, false)

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/show":
			fmt.Fprint(w, `{"model":"sample-model"}`)
		case "/api/status":
			w.WriteHeader(http.StatusNotFound)
			fmt.Fprint(w, `{"error":"not found"}`)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherEditorRunner{paths: []string{"/tmp/stubeditor.json"}}
	restore := OverrideIntegration("stubeditor", stub)
	defer restore()

	DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
		t.Fatalf("unexpected prompt in headless non-yes mode: %q", prompt)
		return false, nil
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubeditor", "--model", "sample-model"})
	err := cmd.Execute()
	if err != nil {
		t.Fatalf("expected launch command to succeed without --yes when an explicit model is provided, got %v", err)
	}
	if diff := compareStringSlices(stub.edited, [][]string{{"sample-model"}}); diff != "" {
		t.Fatalf("unexpected editor writes (-want +got):\n%s", diff)
	}
	if stub.ranModel != "sample-model" {
		t.Fatalf("expected launch to run configured model, got %q", stub.ranModel)
	}
}

func TestLaunchCmdIntegrationArgPromptsForModelWithSavedSelection(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)

	if err := config.SaveIntegration("stubapp", []string{"sample-model"}); err != nil {
		t.Fatalf("failed to seed saved config: %v", err)
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/experimental/model-recommendations":
			fmt.Fprint(w, `{"recommendations":[]}`)
		case "/api/tags":
			fmt.Fprint(w, `{"models":[{"name":"sample-model"},{"name":"qwen3:8b"}]}`)
		case "/api/show":
			fmt.Fprint(w, `{"model":"qwen3:8b"}`)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherSingleRunner{}
	restore := OverrideIntegration("stubapp", stub)
	defer restore()

	oldSelector := DefaultSingleSelector
	defer func() { DefaultSingleSelector = oldSelector }()

	var gotCurrent string
	DefaultSingleSelector = func(title string, items []SelectionItem, current string) (string, error) {
		gotCurrent = current
		return "qwen3:8b", nil
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubapp"})
	if err := cmd.Execute(); err != nil {
		t.Fatalf("launch command failed: %v", err)
	}

	if gotCurrent != "sample-model" {
		t.Fatalf("expected selector current model to be saved model sample-model, got %q", gotCurrent)
	}
	if stub.ranModel != "qwen3:8b" {
		t.Fatalf("expected launch to run selected model qwen3:8b, got %q", stub.ranModel)
	}

	saved, err := config.LoadIntegration("stubapp")
	if err != nil {
		t.Fatalf("failed to reload integration config: %v", err)
	}
	if diff := cmp.Diff([]string{"qwen3:8b"}, saved.Models); diff != "" {
		t.Fatalf("saved models mismatch (-want +got):\n%s", diff)
	}
}

func TestLaunchCmdHeadlessYes_IntegrationRequiresModelEvenWhenSaved(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)
	withLauncherHooks(t)
	withInteractiveSession(t, false)

	if err := config.SaveIntegration("stubapp", []string{"sample-model"}); err != nil {
		t.Fatalf("failed to seed saved config: %v", err)
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/api/show":
			fmt.Fprint(w, `{"model":"sample-model"}`)
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherSingleRunner{}
	restore := OverrideIntegration("stubapp", stub)
	defer restore()

	oldSelector := DefaultSingleSelector
	defer func() { DefaultSingleSelector = oldSelector }()
	DefaultSingleSelector = func(title string, items []SelectionItem, current string) (string, error) {
		t.Fatal("selector should not be called for headless --yes saved-model launch")
		return "", nil
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubapp", "--yes"})
	err := cmd.Execute()
	if err == nil {
		t.Fatal("expected launch command to fail when --yes is used headlessly without --model")
	}
	if !strings.Contains(err.Error(), "requires --model <model>") {
		t.Fatalf("expected actionable --model guidance, got %v", err)
	}
	if stub.ranModel != "" {
		t.Fatalf("expected launch to abort before run, got %q", stub.ranModel)
	}
}

func TestLaunchCmdHeadlessYes_IntegrationWithoutSavedModelReturnsError(t *testing.T) {
	tmpDir := t.TempDir()
	setLaunchTestHome(t, tmpDir)
	withLauncherHooks(t)
	withInteractiveSession(t, false)

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
	}))
	defer srv.Close()
	t.Setenv("OLLAMA_HOST", srv.URL)

	stub := &launcherSingleRunner{}
	restore := OverrideIntegration("stubapp", stub)
	defer restore()

	oldSelector := DefaultSingleSelector
	defer func() { DefaultSingleSelector = oldSelector }()
	DefaultSingleSelector = func(title string, items []SelectionItem, current string) (string, error) {
		t.Fatal("selector should not be called for headless --yes without saved model")
		return "", nil
	}

	cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error { return nil }, func(cmd *cobra.Command) {})
	cmd.SetArgs([]string{"stubapp", "--yes"})
	err := cmd.Execute()
	if err == nil {
		t.Fatal("expected launch command to fail when --yes is used headlessly without --model")
	}
	if !strings.Contains(err.Error(), "requires --model <model>") {
		t.Fatalf("expected actionable --model guidance, got %v", err)
	}
	if stub.ranModel != "" {
		t.Fatalf("expected launch to abort before run, got %q", stub.ranModel)
	}
}
