package bonfire import ( "bytes" "context" "slices" "strings" "testing" ) // peekCases are the global option rows shared by TestPeekGlobals and // TestGlobalFlagsAgree. cobra marks rows whose args cobra itself accepts // (it rejects unknown flags, so those rows are PeekGlobals-only). var peekCases = []struct { name string args []string want Globals cobra bool }{ {"none", []string{"migrate"}, Globals{}, true}, {"-v", []string{"-v", "migrate"}, Globals{Verbosity: 1}, true}, {"-vv", []string{"migrate", "-vv"}, Globals{Verbosity: 2}, true}, {"-vvv", []string{"-vvv", "migrate"}, Globals{Verbosity: 3}, true}, {"-vvvv capped", []string{"-vvvv", "migrate"}, Globals{Verbosity: 3}, true}, {"--verbose twice", []string{"--verbose", "migrate", "--verbose"}, Globals{Verbosity: 2}, true}, {"-v -vv capped", []string{"-v", "migrate", "-vv", "--verbose"}, Globals{Verbosity: 3}, true}, {"-q implies -n", []string{"migrate", "-q"}, Globals{Quiet: true, NoInteraction: true}, true}, {"--quiet", []string{"--quiet", "migrate"}, Globals{Quiet: true, NoInteraction: true}, true}, {"-n", []string{"migrate", "-n"}, Globals{NoInteraction: true}, true}, {"--no-interaction", []string{"--no-interaction", "migrate"}, Globals{NoInteraction: true}, true}, {"--ansi", []string{"migrate", "--ansi"}, Globals{Color: ColorAlways}, true}, {"--no-ansi", []string{"--no-ansi", "migrate"}, Globals{Color: ColorNever}, true}, {"--ansi then --no-ansi", []string{"--ansi", "migrate", "--no-ansi"}, Globals{Color: ColorNever}, true}, {"--no-ansi then --ansi", []string{"--no-ansi", "migrate", "--ansi"}, Globals{Color: ColorAlways}, true}, {"--env X", []string{"--env", "staging", "migrate"}, Globals{Env: "staging"}, true}, {"--env=X", []string{"migrate", "--env=production"}, Globals{Env: "production"}, true}, {"--env takes a dash value", []string{"migrate", "--env", "-v"}, Globals{Env: "-v"}, true}, {"last --env wins", []string{"--env=a", "migrate", "--env", "b"}, Globals{Env: "b"}, true}, {"stop at --", []string{"migrate", "--", "-v", "-q", "--env=x"}, Globals{}, true}, {"combined -qn", []string{"migrate", "-qn"}, Globals{Quiet: true, NoInteraction: true}, true}, {"combined -vq", []string{"-vq", "migrate"}, Globals{Verbosity: 1, Quiet: true, NoInteraction: true}, true}, {"combined -nvv", []string{"migrate", "-nvv"}, Globals{Verbosity: 2, NoInteraction: true}, true}, {"everything", []string{"-vv", "--env=dev", "migrate", "-n", "--no-ansi"}, Globals{Verbosity: 2, NoInteraction: true, Color: ColorNever, Env: "dev"}, true}, {"unknown flags ignored", []string{"--bogus", "-x", "migrate", "-", "--env"}, Globals{}, false}, {"--env without a value", []string{"migrate", "--env"}, Globals{}, false}, {"empty", nil, Globals{}, false}, } // TestPeekGlobals: the tolerant pre-boot scan reads every global option // form, caps verbosity, lets the last color flag win, stops at -- and // never fails on unknown tokens. func TestPeekGlobals(t *testing.T) { for _, tc := range peekCases { t.Run(tc.name, func(t *testing.T) { if got := PeekGlobals(tc.args); got != tc.want { t.Errorf("PeekGlobals(%q) = %+v, want %+v", tc.args, got, tc.want) } }) } } // TestGlobalsArgs: Args re-serialises Globals so PeekGlobals reads them // back unchanged. func TestGlobalsArgs(t *testing.T) { cases := []struct { g Globals want string }{ {Globals{}, ""}, {Globals{Verbosity: 1}, "-v"}, {Globals{Verbosity: 3}, "-vvv"}, {Globals{Verbosity: 7}, "-vvv"}, {Globals{Quiet: true, NoInteraction: true}, "-q"}, {Globals{NoInteraction: true}, "-n"}, {Globals{Color: ColorAlways}, "--ansi"}, {Globals{Color: ColorNever}, "--no-ansi"}, {Globals{Env: "staging"}, "--env=staging"}, {Globals{Verbosity: 2, Quiet: true, NoInteraction: true, Color: ColorNever, Env: "dev"}, "-vv -q --no-ansi --env=dev"}, } for _, tc := range cases { args := tc.g.Args() if got := strings.Join(args, " "); got != tc.want { t.Errorf("%+v.Args() = %q, want %q", tc.g, got, tc.want) } if back := PeekGlobals(args); back != tc.g.normalize() { t.Errorf("PeekGlobals(%q) = %+v, want %+v", args, back, tc.g.normalize()) } } } // TestGlobalFlagsAgree: for every row cobra accepts, the OnGlobals hook // sees exactly the Globals PeekGlobals read before the root existed. func TestGlobalFlagsAgree(t *testing.T) { for _, tc := range peekCases { if !tc.cobra { continue } t.Run(tc.name, func(t *testing.T) { var hooked []Globals var seen Globals cmd := Command{Name: "migrate", Description: "Run the migrations", Run: func(_ context.Context, _ Input, out Output) error { seen = GlobalsOf(out) return nil }} var buf bytes.Buffer root, err := NewRootIO("acme", []Command{cmd}, strings.NewReader(""), &buf, &buf, WithVersion(goldenVersion), OnGlobals(func(g Globals) { hooked = append(hooked, g) })) if err != nil { t.Fatal(err) } if code := Execute(context.Background(), root, slices.Clone(tc.args)); code != ExitOK { t.Fatalf("exit %d:\n%s", code, buf.String()) } peek := PeekGlobals(tc.args) if len(hooked) != 1 || hooked[0] != peek { t.Errorf("hook saw %+v, PeekGlobals %+v", hooked, peek) } if seen != peek { t.Errorf("GlobalsOf(out) = %+v, PeekGlobals %+v", seen, peek) } }) } }