...

Source file src/edge-infra.dev/pkg/sds/emergencyaccess/rules/storage/database/rules_test.go

Documentation: edge-infra.dev/pkg/sds/emergencyaccess/rules/storage/database

     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  				// Return an error immediately from the insert query
    67  				mock.ExpectBegin()
    68  				mock.ExpectExec(datasql.InsertRuleDefault).WithArgs("command", "priv").WillReturnResult(sqlmock.NewErrorResult(fmt.Errorf("insert error")))
    69  				// Rollback is successful
    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  				// Returns an error
    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  			// Contains both insert error and rollback error
    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  						// Missing the privilege row
    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  						// Missing the privilege row
   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  	// &comname, &privname, &comid, &privid
   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  	// &comname, &privname, &comid, &privid
   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  					// All rows are returned
   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