arity_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package main
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/spf13/cobra"
  6. )
  7. // `list` takes nothing. Without a declared arity cobra applies arbitraryArgs, so
  8. // a stray token yielded exit 0 and the command looked like it had done its work —
  9. // a caller could believe a lighting change happened when the argument was silently
  10. // dropped.
  11. func TestZeroArgCommandsRejectStrayTokens(t *testing.T) {
  12. cmds := map[string]*cobra.Command{
  13. "list": NewProfileListCmd(),
  14. }
  15. for name, cmd := range cmds {
  16. t.Run(name, func(t *testing.T) {
  17. err := cmd.ValidateArgs([]string{"unexpected-arg"})
  18. if err == nil {
  19. t.Errorf("%s accepted an argument, want it rejected", name)
  20. return
  21. }
  22. if !strings.Contains(err.Error(), "unexpected-arg") {
  23. t.Errorf("%s error = %q, want it to name the offending argument", name, err)
  24. }
  25. })
  26. }
  27. }
  28. // Rejecting extras must not break the no-argument case.
  29. func TestZeroArgCommandsAcceptNoArgs(t *testing.T) {
  30. cmds := map[string]*cobra.Command{
  31. "list": NewProfileListCmd(),
  32. }
  33. for name, cmd := range cmds {
  34. t.Run(name, func(t *testing.T) {
  35. if err := cmd.ValidateArgs(nil); err != nil {
  36. t.Errorf("%s rejected the empty argument list: %v", name, err)
  37. }
  38. })
  39. }
  40. }
  41. // A command whose arguments are a zone and a value takes exactly those two, and
  42. // refuses a third: an extra token means the invocation is not the one the user
  43. // meant, and carrying on with the first two is a guess.
  44. func TestTwoArgCommandsKeepTheirArity(t *testing.T) {
  45. cmds := map[string]*cobra.Command{
  46. "brightness": NewBrightnessCmd(),
  47. "speed": NewSpeedCmd(),
  48. "color": NewColorCmd(),
  49. }
  50. for name, cmd := range cmds {
  51. t.Run(name, func(t *testing.T) {
  52. if err := cmd.ValidateArgs([]string{"logo", "160"}); err != nil {
  53. t.Errorf("%s rejected its arguments: %v", name, err)
  54. }
  55. if err := cmd.ValidateArgs([]string{"160"}); err == nil {
  56. t.Errorf("%s accepted one argument, want a zone and a value", name)
  57. }
  58. if err := cmd.ValidateArgs([]string{"logo", "160", "extra"}); err == nil {
  59. t.Errorf("%s accepted three arguments, want exactly two", name)
  60. }
  61. })
  62. }
  63. }
  64. // A zone is an argument and not a flag, so the count of a command that writes
  65. // lighting is the thing that keeps it from writing every channel by accident.
  66. func TestZoneCommandsRejectAMissingZone(t *testing.T) {
  67. cmds := map[string]*cobra.Command{
  68. "enable": NewEnableCmd(),
  69. "disable": NewDisableCmd(),
  70. }
  71. for name, cmd := range cmds {
  72. t.Run(name, func(t *testing.T) {
  73. if err := cmd.ValidateArgs([]string{"logo"}); err != nil {
  74. t.Errorf("%s rejected a zone: %v", name, err)
  75. }
  76. err := cmd.ValidateArgs(nil)
  77. if err == nil {
  78. t.Fatalf("%s accepted no arguments, want the zone to be required", name)
  79. }
  80. if !strings.Contains(err.Error(), "zone") {
  81. t.Errorf("%s error = %q, want it to name the missing zone", name, err)
  82. }
  83. })
  84. }
  85. }
  86. // `effect` reads with one argument and writes with two, so the count is what tells
  87. // the two apart, and either count alone has to be accepted.
  88. func TestEffectTakesAZoneAndOptionallyAName(t *testing.T) {
  89. cmd := NewEffectCmd()
  90. if err := cmd.ValidateArgs([]string{"logo"}); err != nil {
  91. t.Errorf("effect rejected a zone on its own: %v", err)
  92. }
  93. if err := cmd.ValidateArgs([]string{"logo", "wave"}); err != nil {
  94. t.Errorf("effect rejected a zone and a name: %v", err)
  95. }
  96. if err := cmd.ValidateArgs(nil); err == nil {
  97. t.Error("effect accepted no arguments, want a zone to be required")
  98. }
  99. if err := cmd.ValidateArgs([]string{"logo", "wave", "extra"}); err == nil {
  100. t.Error("effect accepted three arguments, want at most two")
  101. }
  102. }
  103. // `info` reads every channel when it is given nothing and one zone when it is
  104. // given one. Both are requests, so neither is a stray token.
  105. func TestInfoTakesAtMostAZone(t *testing.T) {
  106. cmd := NewInfoCmd()
  107. if err := cmd.ValidateArgs(nil); err != nil {
  108. t.Errorf("info rejected no arguments: %v", err)
  109. }
  110. if err := cmd.ValidateArgs([]string{"logo"}); err != nil {
  111. t.Errorf("info rejected a zone: %v", err)
  112. }
  113. if err := cmd.ValidateArgs([]string{"logo", "side"}); err == nil {
  114. t.Error("info accepted two arguments, want one zone or none")
  115. }
  116. }
  117. // A list of zones is one argument, so `info logo,side` is one zone naming two
  118. // channels and not two arguments.
  119. func TestInfoTakesAZoneListAsOneArgument(t *testing.T) {
  120. cmd := NewInfoCmd()
  121. if err := cmd.ValidateArgs([]string{"logo,side"}); err != nil {
  122. t.Errorf("info rejected a comma separated zone list: %v", err)
  123. }
  124. }