package test import ( "bytes" "context" "flag" "strings" "testing" "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" lcli "github.com/urfave/cli/v2" ) type MockCLI struct { t *testing.T cmds []*lcli.Command cctx *lcli.Context out *bytes.Buffer } func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command) *MockCLI { // Create a CLI App with an --api-url flag so that we can specify which node // the command should be executed against app := &lcli.App{ Flags: []lcli.Flag{ &lcli.StringFlag{ Name: "api-url", Hidden: true, }, }, Commands: cmds, } var out bytes.Buffer app.Writer = &out app.Setup() cctx := lcli.NewContext(app, &flag.FlagSet{}, nil) cctx.Context = ctx return &MockCLI{t: t, cmds: cmds, cctx: cctx, out: &out} } func (c *MockCLI) Client(addr multiaddr.Multiaddr) *MockCLIClient { return &MockCLIClient{t: c.t, cmds: c.cmds, addr: addr, cctx: c.cctx, out: c.out} } // MockCLIClient runs commands against a particular node type MockCLIClient struct { t *testing.T cmds []*lcli.Command addr multiaddr.Multiaddr cctx *lcli.Context out *bytes.Buffer } func (c *MockCLIClient) RunCmd(input ...string) string { out, err := c.RunCmdRaw(input...) require.NoError(c.t, err, "output:\n%s", out) return out } // Given an input, find the corresponding command or sub-command. // eg "paych add-funds" func (c *MockCLIClient) cmdByNameSub(input []string) (*lcli.Command, []string) { name := input[0] for _, cmd := range c.cmds { if cmd.Name == name { return c.findSubcommand(cmd, input[1:]) } } return nil, []string{} } func (c *MockCLIClient) findSubcommand(cmd *lcli.Command, input []string) (*lcli.Command, []string) { // If there are no sub-commands, return the current command if len(cmd.Subcommands) == 0 { return cmd, input } // Check each sub-command for a match against the name subName := input[0] for _, subCmd := range cmd.Subcommands { if subCmd.Name == subName { // Found a match, recursively search for sub-commands return c.findSubcommand(subCmd, input[1:]) } } return nil, []string{} } func (c *MockCLIClient) RunCmdRaw(input ...string) (string, error) { cmd, input := c.cmdByNameSub(input) if cmd == nil { panic("Could not find command " + input[0] + " " + input[1]) } // prepend --api-url= apiFlag := "--api-url=" + c.addr.String() input = append([]string{apiFlag}, input...) fs := c.flagSet(cmd) err := fs.Parse(input) require.NoError(c.t, err) err = cmd.Action(lcli.NewContext(c.cctx.App, fs, c.cctx)) // Get the output str := strings.TrimSpace(c.out.String()) c.out.Reset() return str, err } func (c *MockCLIClient) flagSet(cmd *lcli.Command) *flag.FlagSet { // Apply app level flags (so we can process --api-url flag) fs := &flag.FlagSet{} for _, f := range c.cctx.App.Flags { err := f.Apply(fs) if err != nil { c.t.Fatal(err) } } // Apply command level flags for _, f := range cmd.Flags { err := f.Apply(fs) if err != nil { c.t.Fatal(err) } } return fs } func (c *MockCLIClient) RunInteractiveCmd(cmd []string, interactive []string) string { c.toStdin(strings.Join(interactive, "\n") + "\n") return c.RunCmd(cmd...) } func (c *MockCLIClient) toStdin(s string) { c.cctx.App.Metadata["stdin"] = bytes.NewBufferString(s) }