1 package server
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "io"
8 "net/http"
9 "net/http/httptest"
10 "strings"
11 "testing"
12
13 "edge-infra.dev/pkg/lib/fog"
14 "edge-infra.dev/pkg/sds/emergencyaccess/apierror"
15 errorhandler "edge-infra.dev/pkg/sds/emergencyaccess/apierror/handler"
16 "edge-infra.dev/pkg/sds/emergencyaccess/eagateway"
17 "edge-infra.dev/pkg/sds/emergencyaccess/middleware"
18 "edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
19 "edge-infra.dev/pkg/sds/emergencyaccess/remotecli"
20 "edge-infra.dev/pkg/sds/emergencyaccess/requestservice"
21 "edge-infra.dev/pkg/sds/emergencyaccess/types"
22
23 "github.com/DATA-DOG/go-sqlmock"
24 "github.com/gin-gonic/gin"
25 "github.com/stretchr/testify/assert"
26 )
27
28 var (
29 defaultCommand = "echo hello"
30 )
31
32 type sendTestRCLI struct {
33 eagateway.RemoteCLI
34
35 commandID string
36 userID string
37 sessionID string
38 request msgdata.Request
39 }
40
41 func (rcli *sendTestRCLI) Send(_ context.Context, userID, sessionID, commandID string, request msgdata.Request, _ ...remotecli.RCLIOption) error {
42 rcli.userID = userID
43 rcli.sessionID = sessionID
44 rcli.commandID = commandID
45 rcli.request = request
46 return nil
47 }
48
49 func createSendRequest(c *gin.Context, target types.Target, sessionID, command string, darkmode bool) (*http.Request, error) {
50 payload := types.SendPayload{
51 Command: command,
52 SessionID: sessionID,
53 Target: target,
54 AuthDetails: types.AuthDetails{
55 DarkMode: darkmode,
56 },
57 }
58 message, err := json.Marshal(payload)
59 if err != nil {
60 return nil, err
61 }
62 req, err := http.NewRequestWithContext(c, http.MethodPost, "/ea/sendCommand", bytes.NewBuffer(message))
63 if err != nil {
64 return nil, err
65 }
66
67 setAuthHeaders(req)
68
69 return req, nil
70 }
71
72 func newRequestService(t *testing.T) *requestservice.RequestService {
73 t.Helper()
74
75 db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
76 assert.NoError(t, err)
77 mock.ExpectQuery(`SELECT value FROM watched_field_objects
78 INNER JOIN watched_field_values
79 ON watched_field_values.object_id = watched_field_objects.object_id
80 INNER JOIN terminals
81 ON watched_field_objects.cluster_edge_id = terminals.cluster_edge_id
82 WHERE watched_field_objects.cluster_edge_id = $1
83 AND kind = 'Node'
84 AND terminals.terminal_id = $2
85 AND jsonpath = '$.metadata.labels["feature.node.kubernetes.io/ien-version"]'
86 AND deleted IS NOT true
87 ORDER BY watched_at DESC
88 LIMIT 1
89 ;`).
90 WithArgs(`storeID`, `terminalID`).
91 WillReturnRows(sqlmock.NewRows([]string{"value"}).
92 AddRow("v1.14.0"),
93 )
94 requestService, err := requestservice.New(db)
95 assert.NoError(t, err)
96
97 t.Cleanup(func() {
98 assert.NoError(t, mock.ExpectationsWereMet())
99 })
100
101 t.Cleanup(func() {
102 db.Close()
103 })
104
105 return requestService
106 }
107
108 func TestSendSuccess(t *testing.T) {
109
110 r := httptest.NewRecorder()
111 gin.SetMode(gin.TestMode)
112 c, ginEngine := gin.CreateTestContext(r)
113
114 authServer, url := authserviceServer(http.StatusOK, WithMiddleware(verifyUserAuthHeaders(t)))
115 defer authServer.Close()
116
117 requestService := newRequestService(t)
118
119 rcli := &sendTestRCLI{}
120 _, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, requestService)
121 assert.NoError(t, err)
122
123
124 sessionID := "TestSendSuccess"
125 target := defaultTarget
126 command := defaultCommand
127 userID := "user"
128 req, err := createSendRequest(c, target, sessionID, command, false)
129 assert.NoError(t, err)
130 ginEngine.ServeHTTP(r, req)
131
132
133 correlationID := r.Result().Header.Get(middleware.CorrelationIDKey)
134
135 expReq, err := msgdata.NewV1_0Request(defaultCommand)
136 assert.NoError(t, err)
137
138 expected := &sendTestRCLI{
139 request: expReq,
140 sessionID: sessionID,
141 commandID: correlationID,
142 userID: userID,
143 }
144 assert.Equal(t, http.StatusOK, r.Result().StatusCode)
145 assert.Equal(t, expected, rcli)
146 }
147
148 func TestSendBadPayload(t *testing.T) {
149
150 r := httptest.NewRecorder()
151 gin.SetMode(gin.TestMode)
152 c, ginEngine := gin.CreateTestContext(r)
153
154 authServer, url := authserviceServer(http.StatusForbidden)
155 defer authServer.Close()
156
157 rcli := &sendTestRCLI{}
158 _, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, nil)
159 assert.NoError(t, err)
160
161
162 req, err := http.NewRequestWithContext(c, http.MethodPost, "/ea/sendCommand", nil)
163 assert.NoError(t, err)
164 ginEngine.ServeHTTP(r, req)
165
166 assert.Equal(t, http.StatusBadRequest, r.Result().StatusCode)
167 assert.Empty(t, rcli)
168 }
169
170 func TestSendUnauthorizedCommand(t *testing.T) {
171
172 r := httptest.NewRecorder()
173 gin.SetMode(gin.TestMode)
174 c, ginEngine := gin.CreateTestContext(r)
175
176 authServer, url := authserviceServer(http.StatusOK)
177 defer authServer.Close()
178
179 requestService := newRequestService(t)
180
181 rcli := &sendTestRCLI{}
182 _, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, requestService)
183 assert.NoError(t, err)
184
185
186 sessionID := "TestSendUnauthorizedCommand"
187 target := defaultTarget
188 command := badCommand
189 req, err := createSendRequest(c, target, sessionID, command, false)
190 assert.NoError(t, err)
191 ginEngine.ServeHTTP(r, req)
192
193 assert.Equal(t, http.StatusForbidden, r.Result().StatusCode)
194 assert.Empty(t, rcli)
195
196
197 body, err := io.ReadAll(r.Result().Body)
198 assert.NoError(t, err)
199 var resp errorhandler.ErrorResponse
200 err = json.Unmarshal(body, &resp)
201 assert.NoError(t, err)
202 assert.Equal(t, apierror.ErrUnauthorizedCommand, resp.ErrorCode)
203 }
204
205
206 func TestDarkmodeUserErrorUnauthorizedCommand(t *testing.T) {
207
208 r := httptest.NewRecorder()
209 gin.SetMode(gin.TestMode)
210 c, ginEngine := gin.CreateTestContext(r)
211
212 authServer, url := authserviceServer(http.StatusOK)
213 defer authServer.Close()
214
215 requestService := newRequestService(t)
216
217 rcli := &sendTestRCLI{}
218 _, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, newLogger(), rcli, requestService)
219 assert.NoError(t, err)
220
221
222 sessionID := "TestDarkmodeUserErrorUnauthorizedCommand"
223 target := defaultTarget
224 command := badCommand
225 req, err := createSendRequest(c, target, sessionID, command, true)
226 assert.NoError(t, err)
227
228 ginEngine.ServeHTTP(r, req)
229 assert.Equal(t, http.StatusForbidden, r.Result().StatusCode)
230
231
232
233 outErr := errorhandler.ParseJSONAPIError(r.Result().Body)
234
235 apiErr, ok := outErr.(apierror.APIError)
236 assert.True(t, ok)
237
238 assert.Equal(t, "", strings.Join(apiErr.UserError(), ""))
239 }
240
241 func TestExtractPayloadSuccess(t *testing.T) {
242 r := httptest.NewRecorder()
243 gin.SetMode(gin.TestMode)
244 c, _ := gin.CreateTestContext(r)
245 sessionID := "TestSendSuccess"
246 command := defaultCommand
247 target := defaultTarget
248 req, err := createSendRequest(c, target, sessionID, command, false)
249 assert.NoError(t, err)
250 c.Request = req
251
252 payload, err := extractSendPayload(c)
253 expected := types.SendPayload{
254 Target: target,
255 SessionID: sessionID,
256 Command: command,
257 }
258 assert.Equal(t, expected, payload)
259 assert.NoError(t, err)
260 }
261
262 func TestExtractPayloadFail(t *testing.T) {
263 r := httptest.NewRecorder()
264 gin.SetMode(gin.TestMode)
265 c, _ := gin.CreateTestContext(r)
266 req, err := createSendRequest(c, types.Target{}, "", "", false)
267 assert.NoError(t, err)
268 c.Request = req
269
270 payload, err := extractSendPayload(c)
271 assert.Equal(t, types.SendPayload{}, payload)
272 assert.Error(t, err)
273 }
274
275 func TestSendAuditLog(t *testing.T) {
276
277 r := httptest.NewRecorder()
278 gin.SetMode(gin.TestMode)
279 c, ginEngine := gin.CreateTestContext(r)
280
281 authServer, url := authserviceServer(http.StatusOK)
282 defer authServer.Close()
283
284 rcli := &sendTestRCLI{}
285
286 b := bytes.Buffer{}
287 log := fog.New(fog.To(&b))
288 _, err := New(eagateway.Config{AuthServiceHost: url}, ginEngine, log, rcli, nil)
289 assert.NoError(t, err)
290
291
292 sessionID := "AuditLogCheck"
293 target := defaultTarget
294 command := defaultCommand
295 req, err := createSendRequest(c, target, sessionID, command, false)
296 assert.NoError(t, err)
297 ginEngine.ServeHTTP(r, req)
298
299 assert.True(t, validateAuditLog(&b, "Send API Called"))
300 }
301
View as plain text