api_test.go raw
1 package api
2
3 import (
4 "context"
5 "fmt"
6 "testing"
7
8 "github.com/stretchr/testify/mock"
9 "github.com/stretchr/testify/require"
10
11 "github.com/getAlby/hub/lnclient"
12 "github.com/getAlby/hub/service"
13 "github.com/getAlby/hub/tests/mocks"
14 )
15
16 func TestGetCustomNodeCommandDefinitions(t *testing.T) {
17 lnClient := mocks.NewMockLNClient(t)
18 svc := mocks.NewMockService(t)
19
20 mockLNCommandDefs := []lnclient.CustomNodeCommandDef{
21 {
22 Name: "no_args",
23 Description: "command without args",
24 Args: nil,
25 },
26 {
27 Name: "with_args",
28 Description: "command with args",
29 Args: []lnclient.CustomNodeCommandArgDef{
30 {Name: "arg1", Description: "first argument"},
31 {Name: "arg2", Description: "second argument"},
32 },
33 },
34 }
35
36 expectedCommands := []CustomNodeCommandDef{
37 {
38 Name: "no_args",
39 Description: "command without args",
40 Args: []CustomNodeCommandArgDef{},
41 },
42 {
43 Name: "with_args",
44 Description: "command with args",
45 Args: []CustomNodeCommandArgDef{
46 {Name: "arg1", Description: "first argument"},
47 {Name: "arg2", Description: "second argument"},
48 },
49 },
50 }
51
52 lnClient.On("GetCustomNodeCommandDefinitions").Return(mockLNCommandDefs)
53 svc.On("GetLNClient").Return(lnClient)
54
55 theAPI := instantiateAPIWithService(svc)
56
57 commands, err := theAPI.GetCustomNodeCommands()
58 require.NoError(t, err)
59 require.NotNil(t, commands)
60 require.ElementsMatch(t, expectedCommands, commands.Commands)
61 }
62
63 func TestExecuteCustomNodeCommand(t *testing.T) {
64 type testCase struct {
65 name string
66 apiCommandLine string
67 lnSupportedCommands []lnclient.CustomNodeCommandDef
68 lnExpectedCommandReq *lnclient.CustomNodeCommandRequest
69 lnResponse *lnclient.CustomNodeCommandResponse
70 lnError error
71 apiExpectedResponse interface{}
72 apiExpectedErr string
73 }
74
75 // Successful execution of a command without args.
76 testCaseOkNoArgs := testCase{
77 name: "command without args",
78 apiCommandLine: "test_command",
79 lnSupportedCommands: []lnclient.CustomNodeCommandDef{{Name: "test_command"}},
80 lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{}},
81 lnResponse: &lnclient.CustomNodeCommandResponse{Response: "ok"},
82 lnError: nil,
83 apiExpectedResponse: "ok",
84 apiExpectedErr: "",
85 }
86
87 // Successful execution of a command with args. The command line contains
88 // different arg value styles: with '=' and with space.
89 testCaseOkWithArgs := testCase{
90 name: "command with args",
91 apiCommandLine: "test_command --arg1=foo --arg2 bar",
92 lnSupportedCommands: []lnclient.CustomNodeCommandDef{
93 {
94 Name: "test_command",
95 Args: []lnclient.CustomNodeCommandArgDef{
96 {Name: "arg1", Description: "argument one"},
97 {Name: "arg2", Description: "argument two"},
98 },
99 },
100 },
101 lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{
102 {Name: "arg1", Value: "foo"},
103 {Name: "arg2", Value: "bar"},
104 }},
105 lnResponse: &lnclient.CustomNodeCommandResponse{Response: "ok"},
106 lnError: nil,
107 apiExpectedResponse: "ok",
108 apiExpectedErr: "",
109 }
110
111 // Successful execution of a command with a possible but unset arg.
112 testCaseOkWithUnsetArg := testCase{
113 name: "command with unset arg",
114 apiCommandLine: "test_command",
115 lnSupportedCommands: []lnclient.CustomNodeCommandDef{
116 {Name: "test_command", Args: []lnclient.CustomNodeCommandArgDef{{Name: "arg1", Description: "argument one"}}},
117 },
118 lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{}},
119 lnResponse: &lnclient.CustomNodeCommandResponse{Response: "ok"},
120 lnError: nil,
121 apiExpectedResponse: "ok",
122 apiExpectedErr: "",
123 }
124
125 // Error: command line is empty.
126 testCaseErrEmptyCommand := testCase{
127 name: "empty command",
128 apiCommandLine: "",
129 lnSupportedCommands: nil,
130 lnExpectedCommandReq: nil,
131 lnResponse: nil,
132 lnError: nil,
133 apiExpectedResponse: nil,
134 apiExpectedErr: "no command provided",
135 }
136
137 // Error: command line is malformed, i.e. non-parseable.
138 testCaseErrMalformedCommand := testCase{
139 name: "command with unclosed quote",
140 apiCommandLine: "test_command\"",
141 lnSupportedCommands: nil,
142 lnExpectedCommandReq: nil,
143 lnResponse: nil,
144 lnError: nil,
145 apiExpectedResponse: nil,
146 apiExpectedErr: "failed to parse node command",
147 }
148
149 // Error: node does not support this command.
150 testCaseErrUnknownCommand := testCase{
151 name: "unknown command",
152 apiCommandLine: "test_command_unknown",
153 lnSupportedCommands: []lnclient.CustomNodeCommandDef{{Name: "test_command"}},
154 lnExpectedCommandReq: nil,
155 lnResponse: nil,
156 lnError: nil,
157 apiExpectedResponse: nil,
158 apiExpectedErr: "unknown command",
159 }
160
161 // Error: unsupported command argument.
162 testCaseErrUnknownArg := testCase{
163 name: "unknown argument",
164 apiCommandLine: "test_command --unknown=fail",
165 lnSupportedCommands: []lnclient.CustomNodeCommandDef{{Name: "test_command"}},
166 lnExpectedCommandReq: nil,
167 lnResponse: nil,
168 lnError: nil,
169 apiExpectedResponse: nil,
170 apiExpectedErr: "flag provided but not defined: -unknown",
171 }
172
173 // Error: the command is valid but the node fails to execute it.
174 testCaseErrNodeFailed := testCase{
175 name: "node failed to execute command",
176 apiCommandLine: "test_command",
177 lnSupportedCommands: []lnclient.CustomNodeCommandDef{{Name: "test_command"}},
178 lnExpectedCommandReq: &lnclient.CustomNodeCommandRequest{Name: "test_command", Args: []lnclient.CustomNodeCommandArg{}},
179 lnResponse: nil,
180 lnError: fmt.Errorf("utter failure"),
181 apiExpectedResponse: nil,
182 apiExpectedErr: "utter failure",
183 }
184
185 testCases := []testCase{
186 testCaseOkNoArgs,
187 testCaseOkWithArgs,
188 testCaseOkWithUnsetArg,
189 testCaseErrEmptyCommand,
190 testCaseErrMalformedCommand,
191 testCaseErrUnknownCommand,
192 testCaseErrUnknownArg,
193 testCaseErrNodeFailed,
194 }
195
196 for _, tc := range testCases {
197 t.Run(tc.name, func(t *testing.T) {
198 lnClient := mocks.NewMockLNClient(t)
199 svc := mocks.NewMockService(t)
200
201 if tc.lnSupportedCommands != nil {
202 lnClient.On("GetCustomNodeCommandDefinitions").Return(tc.lnSupportedCommands)
203 }
204
205 if tc.lnExpectedCommandReq != nil {
206 lnClient.On("ExecuteCustomNodeCommand", mock.Anything, tc.lnExpectedCommandReq).Return(tc.lnResponse, tc.lnError)
207 }
208
209 svc.On("GetLNClient").Return(lnClient)
210
211 theAPI := instantiateAPIWithService(svc)
212
213 response, err := theAPI.ExecuteCustomNodeCommand(context.TODO(), tc.apiCommandLine)
214 require.Equal(t, tc.apiExpectedResponse, response)
215 if tc.apiExpectedErr == "" {
216 require.NoError(t, err)
217 } else {
218 require.ErrorContains(t, err, tc.apiExpectedErr)
219 }
220 })
221 }
222 }
223
224 // instantiateAPIWithService is a helper function that returns a partially
225 // constructed API instance. It is only suitable for the simplest of test cases.
226 func instantiateAPIWithService(s service.Service) *api {
227 return &api{svc: s}
228 }
229