registry_test.go 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package commands
  2. import (
  3. "bytes"
  4. "flag"
  5. "strings"
  6. "testing"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func testRegistry() *Registry {
  11. r := New()
  12. r.Register(&Command{
  13. Name: "served",
  14. ShortDescription: "a command this surface serves",
  15. Callback: func(fs any, a []string, w StringWriter) error {
  16. return w.WriteLine("read " + strings.Join(a, ","))
  17. },
  18. })
  19. r.Register(&Command{
  20. Name: "withheld",
  21. ShortDescription: "a command this surface withholds",
  22. Callback: func(fs any, a []string, w StringWriter) error {
  23. return w.WriteLine("wrote")
  24. },
  25. })
  26. return r
  27. }
  28. // TestFlagUsageGoesToTheWriter pins that a parse failure's usage lands on the command's
  29. // writer.
  30. func TestFlagUsageGoesToTheWriter(t *testing.T) {
  31. r := New()
  32. r.Register(&Command{
  33. Name: "emit",
  34. ShortDescription: "emits",
  35. Flags: func() (*flag.FlagSet, any) {
  36. fl := flag.NewFlagSet("", flag.ContinueOnError)
  37. fl.Bool("json", false, "emit json")
  38. return fl, nil
  39. },
  40. Callback: func(fs any, a []string, w StringWriter) error { return nil },
  41. })
  42. merged := &bytes.Buffer{}
  43. require.Error(t, r.DispatchArgs([]string{"emit", "-nope"}, "", NewStringWriter(merged)))
  44. assert.Contains(t, merged.String(), "flag provided but not defined")
  45. }
  46. // TestDispatchDropsUnparseableLine pins sshd's behaviour for a line shlex rejects: no
  47. // answer at all. An operator with an unbalanced quote gets silence, as they always have.
  48. func TestDispatchDropsUnparseableLine(t *testing.T) {
  49. out := &bytes.Buffer{}
  50. testRegistry().Dispatch(`served "unterminated`, NewStringWriter(out))
  51. assert.Empty(t, out.String())
  52. }
  53. // TestAllSortsByName: registration order does not matter.
  54. func TestAllSortsByName(t *testing.T) {
  55. r := New()
  56. for _, n := range []Name{"zz", Version, "aa", ChangeRemote} {
  57. r.Register(&Command{Name: n})
  58. }
  59. var got []Name
  60. for _, c := range r.All() {
  61. got = append(got, c.Name)
  62. }
  63. assert.Equal(t, []Name{"aa", ChangeRemote, Help, Version, "zz"}, got)
  64. }
  65. // TestHelpFlagAgreesWithHelpCommand pins that -h answers through the registered help
  66. // command. On a clone, help is bound to the source, so a command registered only on the
  67. // clone is known to neither -h nor `help <name>`, and the two never disagree.
  68. func TestHelpFlagAgreesWithHelpCommand(t *testing.T) {
  69. r := testRegistry()
  70. c := r.Clone()
  71. c.Register(&Command{
  72. Name: Logout,
  73. ShortDescription: "ends the session",
  74. Callback: func(fs any, a []string, w StringWriter) error { return nil },
  75. })
  76. flag := &bytes.Buffer{}
  77. c.Dispatch("logout -h", NewStringWriter(flag))
  78. cmd := &bytes.Buffer{}
  79. c.Dispatch("help logout", NewStringWriter(cmd))
  80. assert.Equal(t, cmd.String(), flag.String())
  81. assert.Equal(t, "Command not available logout\n", flag.String())
  82. // And a command both hold is described either way.
  83. flag.Reset()
  84. c.Dispatch("served -h", NewStringWriter(flag))
  85. assert.Equal(t, "served - a command this surface serves\n", flag.String())
  86. }