Skip to content

Commit

Permalink
test: add API tests and mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
rdmitr committed Jan 30, 2025
1 parent d34aacf commit 8410b6b
Show file tree
Hide file tree
Showing 3 changed files with 1,300 additions and 0 deletions.
215 changes: 215 additions & 0 deletions api/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package api

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/getAlby/hub/api/mocks"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/service"
)

func TestGetCustomNodeCommandDefinitions(t *testing.T) {
lnClient := mocks.NewLNClient(t)
svc := mocks.NewService(t)

mockLNCommandDefs := []lnclient.CustomNodeCommandDef{
{
Name: "no_args",
Description: "command without args",
Args: nil,
},
{
Name: "with_args",
Description: "command with args",
Args: []lnclient.CustomNodeCommandArgDef{
{Name: "arg1", Description: "first argument"},
{Name: "arg2", Description: "second argument"},
},
},
}

expectedCommands := []CustomNodeCommandDef{
{
Name: "no_args",
Description: "command without args",
Args: []CustomNodeCommandArgDef{},
},
{
Name: "with_args",
Description: "command with args",
Args: []CustomNodeCommandArgDef{
{Name: "arg1", Description: "first argument"},
{Name: "arg2", Description: "second argument"},
},
},
}

lnClient.On("GetCustomNodeCommandDefinitions").Return(mockLNCommandDefs)
svc.On("GetLNClient").Return(lnClient)

theAPI := instantiateAPIWithService(svc)

commands, err := theAPI.GetCustomNodeCommands()
require.NoError(t, err)
require.NotNil(t, commands)
require.ElementsMatch(t, expectedCommands, commands.Commands)
}

func TestExecuteCustomNodeCommand(t *testing.T) {
type testCase struct {
name string
apiCommandLine string
lnSupportedCommands []lnclient.CustomNodeCommandDef
lnExpectedCommandReq *lnclient.CustomNodeCommandRequest
lnResponse *lnclient.CustomNodeCommandResponse
lnError error
apiExpectedResponse interface{}
apiExpectedErr string
}

// Successful execution of a command without args.
testCaseOkNoArgs := testCase{
name: "command without args",
apiCommandLine: "test_command",
lnSupportedCommands: []lnclient.CustomNodeCommandDef{{Name: "test_command"}},
lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{}},
lnResponse: &lnclient.CustomNodeCommandResponse{Response: "ok"},
lnError: nil,
apiExpectedResponse: "ok",
apiExpectedErr: "",
}

// Successful execution of a command with args. The command line contains
// different arg value styles: with '=' and with space.
testCaseOkWithArgs := testCase{
name: "command with args",
apiCommandLine: "test_command --arg1=foo --arg2 bar",
lnSupportedCommands: []lnclient.CustomNodeCommandDef{
{
Name: "test_command",
Args: []lnclient.CustomNodeCommandArgDef{
{Name: "arg1", Description: "argument one"},
{Name: "arg2", Description: "argument two"},
},
},
},
lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{
{Name: "arg1", Value: "foo"},
{Name: "arg2", Value: "bar"},
}},
lnResponse: &lnclient.CustomNodeCommandResponse{Response: "ok"},
lnError: nil,
apiExpectedResponse: "ok",
apiExpectedErr: "",
}

// Successful execution of a command with a possible but unset arg.
testCaseOkWithUnsetArg := testCase{
name: "command with unset arg",
apiCommandLine: "test_command",
lnSupportedCommands: []lnclient.CustomNodeCommandDef{
{Name: "test_command", Args: []lnclient.CustomNodeCommandArgDef{{Name: "arg1", Description: "argument one"}}},
},
lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{}},
lnResponse: &lnclient.CustomNodeCommandResponse{Response: "ok"},
lnError: nil,
apiExpectedResponse: "ok",
apiExpectedErr: "",
}

// Error: command line is empty.
testCaseErrEmptyCommand := testCase{
name: "empty command",
apiCommandLine: "",
lnSupportedCommands: nil,
lnExpectedCommandReq: nil,
lnResponse: nil,
lnError: nil,
apiExpectedResponse: nil,
apiExpectedErr: "no command provided",
}

// Error: command line is malformed, i.e. non-parseable.
testCaseErrMalformedCommand := testCase{
name: "command with unclosed quote",
apiCommandLine: "test_command\"",
lnSupportedCommands: nil,
lnExpectedCommandReq: nil,
lnResponse: nil,
lnError: nil,
apiExpectedResponse: nil,
apiExpectedErr: "failed to parse node command",
}

// Error: node does not support this command.
testCaseErrUnknownCommand := testCase{
name: "unknown command",
apiCommandLine: "test_command_unknown",
lnSupportedCommands: []lnclient.CustomNodeCommandDef{{Name: "test_command"}},
lnExpectedCommandReq: nil,
lnResponse: nil,
lnError: nil,
apiExpectedResponse: nil,
apiExpectedErr: "unknown command",
}

// Error: the command is valid but the node fails to execute it.
testCaseErrNodeFailed := testCase{
name: "node failed to execute command",
apiCommandLine: "test_command",
lnSupportedCommands: []lnclient.CustomNodeCommandDef{{Name: "test_command"}},
lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{}},
lnResponse: nil,
lnError: fmt.Errorf("utter failure"),
apiExpectedResponse: nil,
apiExpectedErr: "utter failure",
}

testCases := []testCase{
testCaseOkNoArgs,
testCaseOkWithArgs,
testCaseOkWithUnsetArg,
testCaseErrEmptyCommand,
testCaseErrMalformedCommand,
testCaseErrUnknownCommand,
testCaseErrNodeFailed,
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
lnClient := mocks.NewLNClient(t)
svc := mocks.NewService(t)

if tc.lnSupportedCommands != nil {
lnClient.On("GetCustomNodeCommandDefinitions").Return(tc.lnSupportedCommands)
}

if tc.lnExpectedCommandReq != nil {
lnClient.On("ExecuteCustomNodeCommand", mock.Anything, tc.lnExpectedCommandReq).Return(tc.lnResponse, tc.lnError)
}

svc.On("GetLNClient").Return(lnClient)

theAPI := instantiateAPIWithService(svc)

response, err := theAPI.ExecuteCustomNodeCommand(context.TODO(), tc.apiCommandLine)
require.Equal(t, tc.apiExpectedResponse, response)
if tc.apiExpectedErr == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, tc.apiExpectedErr)
}
})
}
}

// instantiateAPIWithService is a helper function that returns a partially
// constructed API instance. It is only suitable for the simplest of test cases.
func instantiateAPIWithService(s service.Service) *api {
return &api{svc: s}
}
Loading

0 comments on commit 8410b6b

Please sign in to comment.