package main import ( "strings" "testing" "github.com/spf13/cobra" ) // `list` takes nothing. Without a declared arity cobra applies arbitraryArgs, so // a stray token yielded exit 0 and the command looked like it had done its work — // a caller could believe a lighting change happened when the argument was silently // dropped. func TestZeroArgCommandsRejectStrayTokens(t *testing.T) { cmds := map[string]*cobra.Command{ "list": NewProfileListCmd(), } for name, cmd := range cmds { t.Run(name, func(t *testing.T) { err := cmd.ValidateArgs([]string{"unexpected-arg"}) if err == nil { t.Errorf("%s accepted an argument, want it rejected", name) return } if !strings.Contains(err.Error(), "unexpected-arg") { t.Errorf("%s error = %q, want it to name the offending argument", name, err) } }) } } // Rejecting extras must not break the no-argument case. func TestZeroArgCommandsAcceptNoArgs(t *testing.T) { cmds := map[string]*cobra.Command{ "list": NewProfileListCmd(), } for name, cmd := range cmds { t.Run(name, func(t *testing.T) { if err := cmd.ValidateArgs(nil); err != nil { t.Errorf("%s rejected the empty argument list: %v", name, err) } }) } } // A command whose arguments are a zone and a value takes exactly those two, and // refuses a third: an extra token means the invocation is not the one the user // meant, and carrying on with the first two is a guess. func TestTwoArgCommandsKeepTheirArity(t *testing.T) { cmds := map[string]*cobra.Command{ "brightness": NewBrightnessCmd(), "speed": NewSpeedCmd(), "color": NewColorCmd(), } for name, cmd := range cmds { t.Run(name, func(t *testing.T) { if err := cmd.ValidateArgs([]string{"logo", "160"}); err != nil { t.Errorf("%s rejected its arguments: %v", name, err) } if err := cmd.ValidateArgs([]string{"160"}); err == nil { t.Errorf("%s accepted one argument, want a zone and a value", name) } if err := cmd.ValidateArgs([]string{"logo", "160", "extra"}); err == nil { t.Errorf("%s accepted three arguments, want exactly two", name) } }) } } // A zone is an argument and not a flag, so the count of a command that writes // lighting is the thing that keeps it from writing every channel by accident. func TestZoneCommandsRejectAMissingZone(t *testing.T) { cmds := map[string]*cobra.Command{ "enable": NewEnableCmd(), "disable": NewDisableCmd(), } for name, cmd := range cmds { t.Run(name, func(t *testing.T) { if err := cmd.ValidateArgs([]string{"logo"}); err != nil { t.Errorf("%s rejected a zone: %v", name, err) } err := cmd.ValidateArgs(nil) if err == nil { t.Fatalf("%s accepted no arguments, want the zone to be required", name) } if !strings.Contains(err.Error(), "zone") { t.Errorf("%s error = %q, want it to name the missing zone", name, err) } }) } } // `effect` reads with one argument and writes with two, so the count is what tells // the two apart, and either count alone has to be accepted. func TestEffectTakesAZoneAndOptionallyAName(t *testing.T) { cmd := NewEffectCmd() if err := cmd.ValidateArgs([]string{"logo"}); err != nil { t.Errorf("effect rejected a zone on its own: %v", err) } if err := cmd.ValidateArgs([]string{"logo", "wave"}); err != nil { t.Errorf("effect rejected a zone and a name: %v", err) } if err := cmd.ValidateArgs(nil); err == nil { t.Error("effect accepted no arguments, want a zone to be required") } if err := cmd.ValidateArgs([]string{"logo", "wave", "extra"}); err == nil { t.Error("effect accepted three arguments, want at most two") } } // `info` reads every channel when it is given nothing and one zone when it is // given one. Both are requests, so neither is a stray token. func TestInfoTakesAtMostAZone(t *testing.T) { cmd := NewInfoCmd() if err := cmd.ValidateArgs(nil); err != nil { t.Errorf("info rejected no arguments: %v", err) } if err := cmd.ValidateArgs([]string{"logo"}); err != nil { t.Errorf("info rejected a zone: %v", err) } if err := cmd.ValidateArgs([]string{"logo", "side"}); err == nil { t.Error("info accepted two arguments, want one zone or none") } } // A list of zones is one argument, so `info logo,side` is one zone naming two // channels and not two arguments. func TestInfoTakesAZoneListAsOneArgument(t *testing.T) { cmd := NewInfoCmd() if err := cmd.ValidateArgs([]string{"logo,side"}); err != nil { t.Errorf("info rejected a comma separated zone list: %v", err) } }