1 package database
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "testing"
8
9 "github.com/DATA-DOG/go-sqlmock"
10 "github.com/stretchr/testify/assert"
11
12 "edge-infra.dev/pkg/lib/fog"
13 rulesengine "edge-infra.dev/pkg/sds/emergencyaccess/rules"
14 datasql "edge-infra.dev/pkg/sds/emergencyaccess/rules/storage/database/sql"
15 )
16
17 func TestAddDefaultRule(t *testing.T) {
18 db, mock := initMockDB(t)
19 defer db.Close()
20 ds := New(fog.New(), db)
21
22 command, priv := "ls", "basic"
23 mock.ExpectBegin()
24 mock.ExpectExec(datasql.InsertRuleDefault).WithArgs(command, priv).WillReturnResult(sqlmock.NewResult(1, 1))
25 mock.ExpectCommit()
26
27 res, err := ds.AddDefaultRules(context.Background(), []rulesengine.RuleSegment{{Command: rulesengine.Command{Name: command}, Privilege: rulesengine.Privilege{Name: priv}}})
28 assert.NoError(t, err)
29 assert.Nil(t, res.Errors)
30 if err := mock.ExpectationsWereMet(); err != nil {
31 t.Errorf("there were unfulfilled expectations: %s", err)
32 }
33 }
34
35 func TestAddDefaultRuleDefer(t *testing.T) {
36 t.Parallel()
37
38 ctx := fog.IntoContext(context.Background(), fog.New(fog.To(io.Discard)))
39
40 tests := map[string]struct {
41 inputRules []rulesengine.RuleSegment
42 expRes rulesengine.AddRuleResult
43
44 expectations func(mock sqlmock.Sqlmock)
45 errorAssertion assert.ErrorAssertionFunc
46 }{
47 "Successful commit": {
48 inputRules: nil,
49 expectations: func(mock sqlmock.Sqlmock) {
50 mock.ExpectBegin()
51 mock.ExpectCommit().WillReturnError(nil)
52 },
53 errorAssertion: assert.NoError,
54 },
55 "Commit Returns Error": {
56 inputRules: nil,
57 expectations: func(mock sqlmock.Sqlmock) {
58 mock.ExpectBegin()
59 mock.ExpectCommit().WillReturnError(fmt.Errorf("commit error"))
60 },
61 errorAssertion: EqualError("error committing transaction: commit error"),
62 },
63 "Rollback on error": {
64 inputRules: []rulesengine.RuleSegment{{Command: rulesengine.Command{Name: "command"}, Privilege: rulesengine.Privilege{Name: "priv"}}},
65 expectations: func(mock sqlmock.Sqlmock) {
66
67 mock.ExpectBegin()
68 mock.ExpectExec(datasql.InsertRuleDefault).WithArgs("command", "priv").WillReturnResult(sqlmock.NewErrorResult(fmt.Errorf("insert error")))
69
70 mock.ExpectRollback()
71 },
72 errorAssertion: EqualError("error getting query result: insert error"),
73 },
74 "Rollback on error returns an error": {
75 inputRules: []rulesengine.RuleSegment{{Command: rulesengine.Command{Name: "command"}, Privilege: rulesengine.Privilege{Name: "priv"}}},
76 expectations: func(mock sqlmock.Sqlmock) {
77 mock.ExpectBegin()
78
79 mock.ExpectExec(datasql.InsertRuleDefault).WithArgs("command", "priv").WillReturnResult(sqlmock.NewErrorResult(fmt.Errorf("insert error")))
80 mock.ExpectRollback().WillReturnError(fmt.Errorf("rollback error"))
81 },
82
83 errorAssertion: EqualError("error rolling back transaction due to application error (error getting query result: insert error): rollback error"),
84 },
85 "Rollback on conflicts": {
86 inputRules: []rulesengine.RuleSegment{{Command: rulesengine.Command{Name: "command"}, Privilege: rulesengine.Privilege{Name: "priv"}}},
87 expRes: rulesengine.AddRuleResult{Errors: []rulesengine.Error{{Privilege: "priv", Type: rulesengine.UnknownPrivilege}}},
88 expectations: func(mock sqlmock.Sqlmock) {
89 mock.ExpectBegin()
90 mock.ExpectExec(datasql.InsertRuleDefault).
91 WithArgs("command", "priv").
92 WillReturnResult(sqlmock.NewResult(0, 0))
93 mock.ExpectQuery(datasql.GetIDsForRuleSegment).
94 WithArgs("command", "priv").
95 WillReturnRows(
96
97 sqlmock.NewRows([]string{"type", "id"}).
98 AddRow("command", "command-uuid"),
99 )
100 mock.ExpectRollback()
101 },
102 errorAssertion: assert.NoError,
103 },
104 "Rollback on Conflicts returns error": {
105 inputRules: []rulesengine.RuleSegment{{Command: rulesengine.Command{Name: "command"}, Privilege: rulesengine.Privilege{Name: "priv"}}},
106 expRes: rulesengine.AddRuleResult{Errors: []rulesengine.Error{{Privilege: "priv", Type: rulesengine.UnknownPrivilege}}},
107
108 expectations: func(mock sqlmock.Sqlmock) {
109 mock.ExpectBegin()
110 mock.ExpectExec(datasql.InsertRuleDefault).
111 WithArgs("command", "priv").
112 WillReturnResult(sqlmock.NewResult(0, 0))
113 mock.ExpectQuery(datasql.GetIDsForRuleSegment).
114 WithArgs("command", "priv").
115 WillReturnRows(
116
117 sqlmock.NewRows([]string{"type", "id"}).
118 AddRow("command", "command-uuid"),
119 )
120 mock.ExpectRollback().WillReturnError(fmt.Errorf("rollback error"))
121 },
122 errorAssertion: EqualError("error rolling back transaction due to errors: rollback error"),
123 },
124 }
125
126 for name, tc := range tests {
127 tc := tc
128 t.Run(name, func(t *testing.T) {
129 t.Parallel()
130 db, mock := initMockDB(t)
131 defer db.Close()
132
133 ds := Dataset{db: db}
134
135 tc.expectations(mock)
136
137 res, err := ds.AddDefaultRules(ctx, tc.inputRules)
138 tc.errorAssertion(t, err)
139 assert.Equal(t, tc.expRes, res)
140
141 assert.NoError(t, mock.ExpectationsWereMet())
142 })
143 }
144 }
145
146 func TestReadAllDefaultRules(t *testing.T) {
147 db, mock := initMockDB(t)
148 defer db.Close()
149 ds := New(fog.New(), db)
150
151 mockRow := sqlmock.NewRows([]string{"comname", "privname", "command_id", "privilege_id"}).AddRow("ls", "basic", "test_comid", "test_privid")
152 mock.ExpectQuery(datasql.SelectAllDefaultRules).WillReturnRows(mockRow)
153
154 res, err := ds.ReadAllDefaultRules(context.Background())
155 assert.NoError(t, err)
156 assert.EqualValues(t, []rulesengine.RuleSegment{{
157 Command: rulesengine.Command{
158 ID: "test_comid",
159 Name: "ls"},
160 Privilege: rulesengine.Privilege{
161 ID: "test_privid",
162 Name: "basic"}}},
163 res)
164 if err := mock.ExpectationsWereMet(); err != nil {
165 t.Errorf("there were unfulfilled expectations: %s", err)
166 }
167 }
168
169 func TestReadDefaultRulesForCommand(t *testing.T) {
170 db, mock := initMockDB(t)
171 defer db.Close()
172 ds := New(fog.New(), db)
173
174 mockRow := sqlmock.NewRows([]string{"comname", "privname", "command_id", "privilege_id"}).AddRow("ls", "basic", "test_comid", "test_privid")
175 mock.ExpectQuery(datasql.SelectDefaultRulesByCommandName).WithArgs("ls").WillReturnRows(mockRow)
176
177 res, err := ds.ReadDefaultRulesForCommand(context.Background(), "ls")
178 assert.NoError(t, err)
179 assert.EqualValues(t, []rulesengine.RuleSegment{{
180 Command: rulesengine.Command{
181 ID: "test_comid",
182 Name: "ls"},
183 Privilege: rulesengine.Privilege{
184 ID: "test_privid",
185 Name: "basic"}}},
186 res)
187 if err := mock.ExpectationsWereMet(); err != nil {
188 t.Errorf("there were unfulfilled expectations: %s", err)
189 }
190 }
191
192 func TestDeleteDefaultRule(t *testing.T) {
193 t.Parallel()
194
195 tests := map[string]struct {
196 commandName string
197 privName string
198
199 expectations func(mock sqlmock.Sqlmock)
200 expRes rulesengine.DeleteResult
201 expErr assert.ErrorAssertionFunc
202 }{
203 "Deleted": {
204 commandName: "ls",
205 privName: "basic",
206 expectations: func(mock sqlmock.Sqlmock) {
207 mock.ExpectExec(datasql.DeleteDefaultRule).
208 WithArgs("ls", "basic").
209 WillReturnResult(sqlmock.NewResult(1, 1))
210 },
211 expRes: rulesengine.DeleteResult{RowsAffected: 1},
212 expErr: assert.NoError,
213 },
214 "Unknown Command": {
215 commandName: "unknownCommand",
216 privName: "basic",
217 expectations: func(mock sqlmock.Sqlmock) {
218 mock.ExpectExec(datasql.DeleteDefaultRule).
219 WithArgs("unknownCommand", "basic").
220 WillReturnResult(sqlmock.NewResult(0, 0))
221
222 mock.ExpectBegin()
223
224 mock.ExpectQuery(datasql.GetIDsForRuleSegment).
225 WithArgs("unknownCommand", "basic").
226 WillReturnRows(sqlmock.NewRows([]string{"type", "id"}).
227 AddRow("privilege", "basic"),
228 )
229 mock.ExpectCommit()
230 },
231 expRes: rulesengine.DeleteResult{RowsAffected: 0, Errors: []rulesengine.Error{{Type: rulesengine.UnknownCommand, Command: "unknownCommand"}}},
232 expErr: assert.NoError,
233 },
234 "Unknown Privilege": {
235 commandName: "ls",
236 privName: "unknownPriv",
237 expectations: func(mock sqlmock.Sqlmock) {
238 mock.ExpectExec(datasql.DeleteDefaultRule).
239 WithArgs("ls", "unknownPriv").
240 WillReturnResult(sqlmock.NewResult(0, 0))
241
242 mock.ExpectBegin()
243
244 mock.ExpectQuery(datasql.GetIDsForRuleSegment).
245 WithArgs("ls", "unknownPriv").
246 WillReturnRows(sqlmock.NewRows([]string{"type", "id"}).
247 AddRow("command", "ls"),
248 )
249 mock.ExpectCommit()
250 },
251 expRes: rulesengine.DeleteResult{RowsAffected: 0, Errors: []rulesengine.Error{{Type: rulesengine.UnknownPrivilege, Privilege: "unknownPriv"}}},
252 expErr: assert.NoError,
253 },
254 "Both Unknown": {
255 commandName: "unknownCommand",
256 privName: "unknownPriv",
257 expectations: func(mock sqlmock.Sqlmock) {
258 mock.ExpectExec(datasql.DeleteDefaultRule).
259 WithArgs("unknownCommand", "unknownPriv").
260 WillReturnResult(sqlmock.NewResult(0, 0))
261
262 mock.ExpectBegin()
263
264 mock.ExpectQuery(datasql.GetIDsForRuleSegment).
265 WithArgs("unknownCommand", "unknownPriv").
266 WillReturnRows(sqlmock.NewRows([]string{"type", "id"}))
267
268 mock.ExpectCommit()
269 },
270 expRes: rulesengine.DeleteResult{
271 RowsAffected: 0,
272 Errors: []rulesengine.Error{
273 {Type: rulesengine.UnknownCommand, Command: "unknownCommand"},
274 {Type: rulesengine.UnknownPrivilege, Privilege: "unknownPriv"},
275 },
276 },
277 expErr: assert.NoError,
278 },
279 "Unknown Rule association": {
280 commandName: "ls",
281 privName: "basic",
282
283 expectations: func(mock sqlmock.Sqlmock) {
284 mock.ExpectExec(datasql.DeleteDefaultRule).
285 WillReturnResult(sqlmock.NewResult(0, 0))
286 mock.ExpectBegin()
287 mock.ExpectQuery(datasql.GetIDsForRuleSegment).
288
289 WillReturnRows(sqlmock.NewRows([]string{"type", "id"}).
290 AddRow("command", "ls").
291 AddRow("privilege", "basic"),
292 )
293 mock.ExpectCommit()
294 },
295
296 expRes: rulesengine.DeleteResult{RowsAffected: 0, Errors: []rulesengine.Error{
297 {Type: rulesengine.UnknownRule, Command: "ls", Privilege: "basic"},
298 }},
299 expErr: assert.NoError,
300 },
301 }
302
303 for name, tc := range tests {
304 tc := tc
305 t.Run(name, func(t *testing.T) {
306 t.Parallel()
307
308 db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
309 assert.NoError(t, err)
310
311 tc.expectations(mock)
312
313 ds := Dataset{db: db}
314
315 res, err := ds.DeleteDefaultRule(context.Background(), tc.commandName, tc.privName)
316 tc.expErr(t, err)
317 assert.Equal(t, tc.expRes, res)
318
319 assert.NoError(t, mock.ExpectationsWereMet())
320 })
321 }
322 }
323
324 func TestAppendRule(t *testing.T) {
325 rulesObjects := []rulesengine.RuleSegment{
326 {
327 Command: rulesengine.Command{ID: "testcomID1", Name: "com1"},
328 Privilege: rulesengine.Privilege{ID: "testPrivID1", Name: "priv1"},
329 },
330 {
331 Command: rulesengine.Command{ID: "testcomID1", Name: "com1"},
332 Privilege: rulesengine.Privilege{ID: "testPrivID2", Name: "priv2"},
333 },
334 {
335 Command: rulesengine.Command{ID: "testcomID2", Name: "com2"},
336 Privilege: rulesengine.Privilege{ID: "testPrivID3", Name: "priv3"},
337 },
338 }
339 expectedRules := []rulesengine.Rule{
340 {
341 Command: rulesengine.Command{ID: "testcomID1", Name: "com1"},
342 Privileges: []rulesengine.Privilege{
343 {
344 ID: "testPrivID1", Name: "priv1",
345 },
346 {
347 ID: "testPrivID2", Name: "priv2",
348 },
349 },
350 },
351 {
352 Command: rulesengine.Command{ID: "testcomID2", Name: "com2"},
353 Privileges: []rulesengine.Privilege{
354 {
355 ID: "testPrivID3", Name: "priv3",
356 },
357 },
358 },
359 }
360 actualRules := assembleRules(rulesObjects)
361 assert.EqualValues(t, expectedRules, actualRules)
362 }
363
364 func TestCheckUnknownDeleteRuleErrs(t *testing.T) {
365 t.Parallel()
366
367 tests := map[string]struct {
368 segment rulesengine.RuleSegment
369 expErrs []rulesengine.Error
370 }{
371 "UnknownCommand": {
372 segment: rulesengine.RuleSegment{
373 Command: rulesengine.Command{Name: "unknownCommand"},
374 Privilege: rulesengine.Privilege{
375 Name: "knownPriv",
376 ID: "privID",
377 },
378 },
379 expErrs: []rulesengine.Error{{Type: rulesengine.UnknownCommand, Command: "unknownCommand"}},
380 },
381 "UnknownPrivilege": {
382 segment: rulesengine.RuleSegment{
383 Command: rulesengine.Command{
384 Name: "knownCommand",
385 ID: "commandID",
386 },
387 Privilege: rulesengine.Privilege{Name: "unknownPriv"},
388 },
389 expErrs: []rulesengine.Error{{Type: rulesengine.UnknownPrivilege, Privilege: "unknownPriv"}},
390 },
391 "UnknownCommand and UnknownPrivilege": {
392 segment: rulesengine.RuleSegment{
393 Command: rulesengine.Command{Name: "unknownCommand"},
394 Privilege: rulesengine.Privilege{Name: "unknownPriv"},
395 },
396 expErrs: []rulesengine.Error{
397 {Type: rulesengine.UnknownCommand, Command: "unknownCommand"},
398 {Type: rulesengine.UnknownPrivilege, Privilege: "unknownPriv"}},
399 },
400 "UnknownRule": {
401 segment: rulesengine.RuleSegment{
402 Command: rulesengine.Command{
403 Name: "knownCommand",
404 ID: "commandID",
405 },
406 Privilege: rulesengine.Privilege{
407 Name: "knownPriv",
408 ID: "privID",
409 },
410 },
411 expErrs: []rulesengine.Error{{Type: rulesengine.UnknownRule, Command: "knownCommand", Privilege: "knownPriv"}},
412 },
413 }
414
415 for name, tc := range tests {
416 tc := tc
417 t.Run(name, func(t *testing.T) {
418 t.Parallel()
419
420 errs := checkUnknownDeleteRuleErrs(tc.segment)
421 assert.Equal(t, tc.expErrs, errs)
422 })
423 }
424 }
425
View as plain text