github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/graveler/branch/protection_manager_test.go (about)

     1  package branch_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"errors"
     7  	"testing"
     8  
     9  	"github.com/go-openapi/swag"
    10  	"github.com/go-test/deep"
    11  	"github.com/golang/mock/gomock"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/treeverse/lakefs/pkg/graveler"
    14  	"github.com/treeverse/lakefs/pkg/graveler/branch"
    15  	"github.com/treeverse/lakefs/pkg/graveler/mock"
    16  	"github.com/treeverse/lakefs/pkg/graveler/settings"
    17  	"github.com/treeverse/lakefs/pkg/kv/kvtest"
    18  	"github.com/treeverse/lakefs/pkg/testutil"
    19  )
    20  
    21  var repository = &graveler.RepositoryRecord{
    22  	RepositoryID: "example-repo",
    23  	Repository: &graveler.Repository{
    24  		StorageNamespace: "mem://my-storage",
    25  		DefaultBranchID:  "main",
    26  	},
    27  }
    28  
    29  func TestSetAndGet(t *testing.T) {
    30  	ctx := context.Background()
    31  	bpm := prepareTest(t, ctx)
    32  	_, eTag, err := bpm.GetRules(ctx, repository)
    33  	require.NoError(t, err)
    34  	require.NotNil(t, eTag)
    35  	require.Equal(t, "", *eTag)
    36  
    37  	err = bpm.SetRules(ctx, repository, &graveler.BranchProtectionRules{
    38  		BranchPatternToBlockedActions: map[string]*graveler.BranchProtectionBlockedActions{
    39  			"main*": {
    40  				Value: []graveler.BranchProtectionBlockedAction{
    41  					graveler.BranchProtectionBlockedAction_STAGING_WRITE,
    42  				},
    43  			},
    44  		},
    45  	}, eTag)
    46  	testutil.Must(t, err)
    47  
    48  	rules, _, err := bpm.GetRules(ctx, repository)
    49  	testutil.Must(t, err)
    50  
    51  	if len(rules.BranchPatternToBlockedActions) != 1 {
    52  		t.Fatalf("expected 1 rule, got %d", len(rules.BranchPatternToBlockedActions))
    53  	}
    54  	expectedActions := &graveler.BranchProtectionBlockedActions{Value: []graveler.BranchProtectionBlockedAction{graveler.BranchProtectionBlockedAction_STAGING_WRITE}}
    55  	if diff := deep.Equal(expectedActions, rules.BranchPatternToBlockedActions["main*"]); diff != nil {
    56  		t.Fatalf("got unexpected blocked actions. diff=%s", diff)
    57  	}
    58  }
    59  
    60  func TestSetWrongETag(t *testing.T) {
    61  	ctx := context.Background()
    62  	bpm := prepareTest(t, ctx)
    63  	err := bpm.SetRules(ctx, repository, &graveler.BranchProtectionRules{
    64  		BranchPatternToBlockedActions: map[string]*graveler.BranchProtectionBlockedActions{
    65  			"main*": {
    66  				Value: []graveler.BranchProtectionBlockedAction{
    67  					graveler.BranchProtectionBlockedAction_STAGING_WRITE,
    68  				},
    69  			},
    70  		},
    71  	}, swag.String(base64.StdEncoding.EncodeToString([]byte("WRONG_ETAG"))))
    72  	if !errors.Is(err, graveler.ErrPreconditionFailed) {
    73  		t.Fatalf("expected ErrPreconditionFailed, got %v", err)
    74  	}
    75  }
    76  
    77  func TestIsBlocked(t *testing.T) {
    78  	ctx := context.Background()
    79  	var (
    80  		action1 = graveler.BranchProtectionBlockedAction_STAGING_WRITE
    81  		action2 = graveler.BranchProtectionBlockedAction_COMMIT
    82  		action3 = graveler.BranchProtectionBlockedAction(2)
    83  		action4 = graveler.BranchProtectionBlockedAction(3)
    84  	)
    85  	tests := map[string]struct {
    86  		patternToBlockedActions map[string]*graveler.BranchProtectionBlockedActions
    87  		expectedBlockedActions  map[string]*graveler.BranchProtectionBlockedActions
    88  		expectedAllowedActions  map[string]*graveler.BranchProtectionBlockedActions
    89  	}{
    90  		"two_rules": {
    91  			patternToBlockedActions: map[string]*graveler.BranchProtectionBlockedActions{"main*": {Value: []graveler.BranchProtectionBlockedAction{action1}}, "dev": {Value: []graveler.BranchProtectionBlockedAction{action2}}},
    92  			expectedBlockedActions:  map[string]*graveler.BranchProtectionBlockedActions{"main": {Value: []graveler.BranchProtectionBlockedAction{action1}}, "main2": {Value: []graveler.BranchProtectionBlockedAction{action1}}, "dev": {Value: []graveler.BranchProtectionBlockedAction{action2}}},
    93  			expectedAllowedActions:  map[string]*graveler.BranchProtectionBlockedActions{"main": {Value: []graveler.BranchProtectionBlockedAction{action2}}, "main2": {Value: []graveler.BranchProtectionBlockedAction{action2}}, "dev": {Value: []graveler.BranchProtectionBlockedAction{action1}}, "dev1": {Value: []graveler.BranchProtectionBlockedAction{action1, action2}}},
    94  		},
    95  		"multiple_blocked": {
    96  			patternToBlockedActions: map[string]*graveler.BranchProtectionBlockedActions{"main*": {Value: []graveler.BranchProtectionBlockedAction{action1, action2, action3}}, "stable_*": {Value: []graveler.BranchProtectionBlockedAction{action3, action4}}},
    97  			expectedBlockedActions:  map[string]*graveler.BranchProtectionBlockedActions{"main": {Value: []graveler.BranchProtectionBlockedAction{action1, action2, action3}}, "main2": {Value: []graveler.BranchProtectionBlockedAction{action1, action2, action3}}, "stable_branch": {Value: []graveler.BranchProtectionBlockedAction{action3, action4}}},
    98  			expectedAllowedActions:  map[string]*graveler.BranchProtectionBlockedActions{"main": {Value: []graveler.BranchProtectionBlockedAction{action4}}, "main2": {Value: []graveler.BranchProtectionBlockedAction{action4}}, "stable_branch": {Value: []graveler.BranchProtectionBlockedAction{action1, action2}}},
    99  		},
   100  		"overlapping_patterns": {
   101  			patternToBlockedActions: map[string]*graveler.BranchProtectionBlockedActions{"main*": {Value: []graveler.BranchProtectionBlockedAction{action1}}, "mai*": {Value: []graveler.BranchProtectionBlockedAction{action2}}, "ma*": {Value: []graveler.BranchProtectionBlockedAction{action2, action3}}},
   102  			expectedBlockedActions:  map[string]*graveler.BranchProtectionBlockedActions{"main": {Value: []graveler.BranchProtectionBlockedAction{action1, action2, action3}}},
   103  			expectedAllowedActions:  map[string]*graveler.BranchProtectionBlockedActions{"main": {Value: []graveler.BranchProtectionBlockedAction{action4}}},
   104  		},
   105  		"no_rules": {
   106  			expectedAllowedActions: map[string]*graveler.BranchProtectionBlockedActions{"main": {Value: []graveler.BranchProtectionBlockedAction{action1, action2}}},
   107  		},
   108  	}
   109  	for name, tst := range tests {
   110  		t.Run(name, func(t *testing.T) {
   111  			bpm := prepareTest(t, ctx)
   112  			testutil.Must(t, bpm.SetRules(ctx, repository, &graveler.BranchProtectionRules{
   113  				BranchPatternToBlockedActions: tst.patternToBlockedActions,
   114  			}, nil))
   115  
   116  			for branchID, expectedBlockedActions := range tst.expectedBlockedActions {
   117  				for _, action := range expectedBlockedActions.Value {
   118  					res, err := bpm.IsBlocked(ctx, repository, graveler.BranchID(branchID), action)
   119  					testutil.Must(t, err)
   120  					if !res {
   121  						t.Fatalf("branch %s action %s expected to be blocked, but was allowed", branchID, action)
   122  					}
   123  				}
   124  			}
   125  			for branchID, expectedAllowedActions := range tst.expectedAllowedActions {
   126  				for _, action := range expectedAllowedActions.Value {
   127  					res, err := bpm.IsBlocked(ctx, repository, graveler.BranchID(branchID), action)
   128  					testutil.Must(t, err)
   129  					if res {
   130  						t.Fatalf("branch %s action %s expected to be allowed, but was blocked", branchID, action)
   131  					}
   132  				}
   133  			}
   134  		})
   135  	}
   136  }
   137  
   138  func prepareTest(t *testing.T, ctx context.Context) *branch.ProtectionManager {
   139  	ctrl := gomock.NewController(t)
   140  	refManager := mock.NewMockRefManager(ctrl)
   141  	branchLock := mock.NewMockBranchLocker(ctrl)
   142  	cb := func(_ context.Context, _ *graveler.RepositoryRecord, _ graveler.BranchID, f func() (interface{}, error)) (interface{}, error) {
   143  		return f()
   144  	}
   145  	branchLock.EXPECT().MetadataUpdater(ctx, gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(cb).AnyTimes()
   146  	refManager.EXPECT().GetRepository(ctx, gomock.Any()).AnyTimes().Return(repository, nil)
   147  	kvStore := kvtest.GetStore(ctx, t)
   148  	m := settings.NewManager(refManager, kvStore)
   149  
   150  	return branch.NewProtectionManager(m)
   151  }