// Copyright 2026 printing-press-golden. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.

package cobratree

import (
	"reflect"
	"strings"
	"testing"
)

// TestSplitShellArgs pins the whitespace + double-quote splitting used by
// the args-field passthrough. Behavior we rely on: bare whitespace splits,
// tabs split, double-quoted spans stay together, empty input yields nil.
func TestSplitShellArgs(t *testing.T) {
	cases := []struct {
		name string
		in   string
		want []string
	}{
		{"empty", "", nil},
		{"single token", "contacts", []string{"contacts"}},
		{"two tokens", "inbox health", []string{"inbox", "health"}},
		{"extra whitespace", "  foo   bar  ", []string{"foo", "bar"}},
		{"tabs", "foo\tbar", []string{"foo", "bar"}},
		{"quoted token", `"hello world"`, []string{"hello world"}},
		{"mixed quoted and bare", `contacts "john doe" active`, []string{"contacts", "john doe", "active"}},
	}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			got := splitShellArgs(tc.in)
			if !reflect.DeepEqual(got, tc.want) {
				t.Errorf("splitShellArgs(%q) = %v, want %v", tc.in, got, tc.want)
			}
		})
	}
}

// TestCliArgsFromMCP_BlocksRootFlags pins the structured-parameter half of
// the control-plane-injection guard: even when an MCP client wraps the
// flag in the structured args map (instead of the free-form "args"
// string), the root flags listed in blockedRootFlags must be dropped
// before they reach exec.CommandContext. A regression here would let a
// caller redirect --base-url, swap --token, or load a malicious --config.
func TestCliArgsFromMCP_BlocksRootFlags(t *testing.T) {
	in := map[string]any{
		"args":     "contacts",
		"base-url": "https://evil.example.com",
		"config":   "/tmp/evil.yaml",
		"deliver":  "fd:3",
		"profile":  "attacker",
		"token":    "stolen-token",
		// Allowed per-command flag passes through.
		"limit": float64(10),
	}
	got := cliArgsFromMCP(in)
	want := []string{"--limit", "10"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("cliArgsFromMCP dropped/kept wrong keys: got %v, want %v", got, want)
	}
	for _, blocked := range []string{"--base-url", "--config", "--deliver", "--profile", "--token", "--args"} {
		for _, tok := range got {
			if tok == blocked {
				t.Errorf("blocked flag %q leaked through cliArgsFromMCP", blocked)
			}
		}
	}
}

// TestCliArgsFromMCP_AllowsPerCommandFlags is the don't-overcorrect half:
// any flag NOT in blockedRootFlags must still pass through, including
// strings, bools, numbers, and []any. Without this, a tightening change
// to the blocklist that accidentally drops legitimate per-command flags
// would silently break every MCP-driven command. cliArgsFromMCP also
// guarantees sorted-key output (so generated commands see deterministic
// argv ordering); compare directly to catch a regression in either the
// blocklist or the sort.
func TestCliArgsFromMCP_AllowsPerCommandFlags(t *testing.T) {
	in := map[string]any{
		"query":   "alpha",
		"verbose": true,
		"limit":   float64(25),
		"tags":    []any{"a", "b"},
	}
	got := cliArgsFromMCP(in)
	want := []string{"--limit", "25", "--query", "alpha", "--tags", "a,b", "--verbose"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("cliArgsFromMCP per-command passthrough: got %v, want %v", got, want)
	}
}

// TestArgsFieldRejectsFlagLikeTokens covers the free-form "args" string
// half of the control-plane-injection guard. shellOutToCLI is a closure
// that requires a real binary on PATH; we exercise the same guard logic
// here so a regression that drops the strings.HasPrefix("-") check is
// caught at unit-test scope rather than only via end-to-end MCP runs.
func TestArgsFieldRejectsFlagLikeTokens(t *testing.T) {
	guard := func(raw string) (rejected string, ok bool) {
		for _, t := range splitShellArgs(raw) {
			if strings.HasPrefix(t, "-") {
				return t, false
			}
		}
		return "", true
	}
	cases := []struct {
		name        string
		in          string
		wantOK      bool
		wantBlocked string
	}{
		{"long flag", "--config /tmp/evil.yaml", false, "--config"},
		{"short flag", "-c", false, "-c"},
		{"bare double dash", "--", false, "--"},
		{"flag with equals", "--config=/tmp/evil.yaml", false, "--config=/tmp/evil.yaml"},
		{"flag mid-args", "contacts --verbose", false, "--verbose"},
		{"clean positional", "contacts", true, ""},
		{"two positionals", "inbox health", true, ""},
		{"empty", "", true, ""},
		// Shell metachars in unquoted positional content stay verbatim;
		// the CLI is invoked via exec.CommandContext (no /bin/sh), so
		// $VAR / && / | are inert. Pin that they don't trip the guard.
		{"shell metachars in positional", `name with $VAR && pipe|stuff`, true, ""},
	}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			tok, ok := guard(tc.in)
			if ok != tc.wantOK {
				t.Errorf("guard(%q) ok = %v, want %v", tc.in, ok, tc.wantOK)
			}
			if !tc.wantOK && tok != tc.wantBlocked {
				t.Errorf("guard(%q) blocked = %q, want %q", tc.in, tok, tc.wantBlocked)
			}
		})
	}
}
