storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/replication/replication_test.go (about)

     1  package replication
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"testing"
     7  )
     8  
     9  func TestParseAndValidateReplicationConfig(t *testing.T) {
    10  	testCases := []struct {
    11  		inputConfig           string
    12  		expectedParsingErr    error
    13  		expectedValidationErr error
    14  		destBucket            string
    15  		sameTarget            bool
    16  	}{
    17  		{ //1 Invalid delete marker status in replication config
    18  			inputConfig:           `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>string</Status></DeleteMarkerReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    19  			destBucket:            "destinationbucket",
    20  			sameTarget:            false,
    21  			expectedParsingErr:    nil,
    22  			expectedValidationErr: errInvalidDeleteMarkerReplicationStatus,
    23  		},
    24  		//2 Invalid delete replication status in replication config
    25  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    26  			destBucket:            "destinationbucket",
    27  			sameTarget:            false,
    28  			expectedParsingErr:    nil,
    29  			expectedValidationErr: errDeleteReplicationMissing,
    30  		},
    31  		//3 valid replication config
    32  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    33  			destBucket:            "destinationbucket",
    34  			sameTarget:            false,
    35  			expectedParsingErr:    nil,
    36  			expectedValidationErr: nil,
    37  		},
    38  		//4 missing role in config
    39  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    40  			// destination bucket in config different from bucket specified
    41  			destBucket:            "destinationbucket",
    42  			sameTarget:            false,
    43  			expectedParsingErr:    nil,
    44  			expectedValidationErr: errRoleArnMissing,
    45  		},
    46  		//5 replication destination in different rules not identical
    47  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><Priority>3</Priority><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket2</Bucket></Destination></Rule></ReplicationConfiguration>`,
    48  			destBucket:            "destinationbucket",
    49  			sameTarget:            false,
    50  			expectedParsingErr:    nil,
    51  			expectedValidationErr: errReplicationDestinationMismatch,
    52  		},
    53  		//6 missing rule status in replication config
    54  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    55  			destBucket:            "destinationbucket",
    56  			sameTarget:            false,
    57  			expectedParsingErr:    nil,
    58  			expectedValidationErr: errEmptyRuleStatus,
    59  		},
    60  		//7 invalid rule status in replication config
    61  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enssabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    62  			destBucket:            "destinationbucket",
    63  			sameTarget:            false,
    64  			expectedParsingErr:    nil,
    65  			expectedValidationErr: errInvalidRuleStatus,
    66  		},
    67  		//8 invalid rule id exceeds length allowed in replication config
    68  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><ID>vsUVERgOc8zZYagLSzSa5lE8qeI6nh1lyLNS4R9W052yfecrhhepGboswSWMMNO8CPcXM4GM3nKyQ72EadlMzzZBFoYWKn7ju5GoE5w9c57a0piHR1vexpdd9FrMquiruvAJ0MTGVupm0EegMVxoIOdjx7VgZhGrmi2XDvpVEFT7WmYMA9fSK297XkTHWyECaNHBySJ1Qp4vwX8tPNauKpfHx4kzUpnKe1PZbptGMWbY5qTcwlNuMhVSmgFffShq</ID><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    69  			destBucket:            "destinationbucket",
    70  			sameTarget:            false,
    71  			expectedParsingErr:    nil,
    72  			expectedValidationErr: errInvalidRuleID,
    73  		},
    74  		//9 invalid priority status in replication config
    75  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
    76  			destBucket:            "destinationbucket",
    77  			sameTarget:            false,
    78  			expectedParsingErr:    nil,
    79  			expectedValidationErr: errReplicationUniquePriority,
    80  		},
    81  		//10 no rule in replication config
    82  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role></ReplicationConfiguration>`,
    83  			destBucket:            "destinationbucket",
    84  			sameTarget:            false,
    85  			expectedParsingErr:    nil,
    86  			expectedValidationErr: errReplicationNoRule,
    87  		},
    88  		//11 no destination in replication config
    89  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination></Destination></Rule></ReplicationConfiguration>`,
    90  			destBucket:            "destinationbucket",
    91  			sameTarget:            false,
    92  			expectedParsingErr:    Errorf("invalid destination '%v'", ""),
    93  			expectedValidationErr: nil,
    94  		},
    95  		//12 destination not matching ARN in replication config
    96  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>destinationbucket2</Bucket></Destination></Rule></ReplicationConfiguration>`,
    97  			destBucket:            "destinationbucket",
    98  			sameTarget:            false,
    99  			expectedParsingErr:    fmt.Errorf("invalid destination '%v'", "destinationbucket2"),
   100  			expectedValidationErr: nil,
   101  		},
   102  	}
   103  	for i, tc := range testCases {
   104  		t.Run(fmt.Sprintf("Test %d", i+1), func(t *testing.T) {
   105  			cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig)))
   106  			if err != nil && tc.expectedParsingErr != nil && err.Error() != tc.expectedParsingErr.Error() {
   107  				t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err)
   108  			}
   109  			if err == nil && tc.expectedParsingErr != nil {
   110  				t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err)
   111  			}
   112  			if tc.expectedParsingErr != nil {
   113  				// We already expect a parsing error,
   114  				// no need to continue this test.
   115  				return
   116  			}
   117  			err = cfg.Validate(tc.destBucket, tc.sameTarget)
   118  			if err != tc.expectedValidationErr {
   119  				t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedValidationErr, err)
   120  			}
   121  		})
   122  	}
   123  }
   124  func TestReplicate(t *testing.T) {
   125  	cfgs := []Config{
   126  		{ //Config0 - Replication config has no filters, all replication enabled
   127  			Rules: []Rule{
   128  				{
   129  					Status:                  Enabled,
   130  					Priority:                3,
   131  					DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
   132  					DeleteReplication:       DeleteReplication{Status: Enabled},
   133  					Filter:                  Filter{},
   134  				},
   135  			},
   136  		},
   137  		{ //Config1 - Replication config has no filters, delete,delete-marker replication disabled
   138  			Rules: []Rule{
   139  				{
   140  					Status:                  Enabled,
   141  					Priority:                3,
   142  					DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
   143  					DeleteReplication:       DeleteReplication{Status: Disabled},
   144  					Filter:                  Filter{},
   145  				},
   146  			},
   147  		},
   148  		{ //Config2 - Replication config has filters and more than 1 matching rule, delete,delete-marker replication disabled
   149  			Rules: []Rule{
   150  				{
   151  					Status:                  Enabled,
   152  					Priority:                2,
   153  					DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
   154  					DeleteReplication:       DeleteReplication{Status: Enabled},
   155  					Filter:                  Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}},
   156  				},
   157  				{
   158  					Status:                  Enabled,
   159  					Priority:                1,
   160  					DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
   161  					DeleteReplication:       DeleteReplication{Status: Disabled},
   162  					Filter:                  Filter{Prefix: "xyz"},
   163  				},
   164  			},
   165  		},
   166  		{ //Config3 - Replication config has filters and no overlapping rules
   167  			Rules: []Rule{
   168  				{
   169  					Status:                  Enabled,
   170  					Priority:                2,
   171  					DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
   172  					DeleteReplication:       DeleteReplication{Status: Enabled},
   173  					Filter:                  Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}},
   174  				},
   175  				{
   176  					Status:                  Enabled,
   177  					Priority:                1,
   178  					DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
   179  					DeleteReplication:       DeleteReplication{Status: Disabled},
   180  					Filter:                  Filter{Prefix: "abc"},
   181  				},
   182  			},
   183  		},
   184  	}
   185  	testCases := []struct {
   186  		opts           ObjectOpts
   187  		c              Config
   188  		expectedResult bool
   189  	}{
   190  		// using config 1 - no filters, all replication enabled
   191  		{ObjectOpts{}, cfgs[0], false},                                //1. invalid ObjectOpts missing object name
   192  		{ObjectOpts{Name: "c1test"}, cfgs[0], true},                   //2. valid ObjectOpts passing empty Filter
   193  		{ObjectOpts{Name: "c1test", VersionID: "vid"}, cfgs[0], true}, //3. valid ObjectOpts passing empty Filter
   194  
   195  		{ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true},                                //4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status
   196  		{ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true},                                  //5. permanent delete of version, matches DeleteReplication status - valid case
   197  		{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true},              //6. permanent delete of version, matches DeleteReplication status
   198  		{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, //7. permanent delete of version, disqualified by SSE-C
   199  		{ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false},                   //8. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C
   200  		{ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], false},                                                                      //9. replication of SSE-C encrypted object, disqualified
   201  
   202  		//  using config 2 - no filters, only replication of object, metadata enabled
   203  		{ObjectOpts{Name: "c2test"}, cfgs[1], true},                                                                                   //10. valid ObjectOpts passing empty Filter
   204  		{ObjectOpts{Name: "c2test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false},                               //11. DeleteMarker version replication  not allowed due to DeleteMarkerReplication status
   205  		{ObjectOpts{Name: "c2test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[1], false},                                 //12. permanent delete of version, disallowed by DeleteReplication status
   206  		{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false},             //13. permanent delete of DeleteMarker version, disallowed by DeleteReplication status
   207  		{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, //14. permanent delete of version, disqualified by SSE-C & DeleteReplication status
   208  		{ObjectOpts{Name: "c2test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false},                   //15. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C & DeleteMarkerReplication status
   209  		{ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], false},                                                                      //16. replication of SSE-C encrypted object, disqualified by default
   210  		// using config 2 - has more than one rule with overlapping prefixes
   211  		{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v1"}, cfgs[2], true},                                                                       //17. matches rule 1 for replication of content/metadata
   212  		{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1"}, cfgs[2], true},                                                                      //18. matches rule 1 for replication of content/metadata
   213  		{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], false},                  //19. matches rule 1 - DeleteMarker replication disallowed by rule
   214  		{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], true}, //20. matches rule 1 - DeleteReplication allowed by rule for permanent delete of DeleteMarker
   215  		{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], true},                     //21. matches rule 1 - DeleteReplication allowed by rule for permanent delete of version
   216  		{ObjectOpts{Name: "xyz/c3test"}, cfgs[2], true},                                                                                         //22. matches rule 2 for replication of content/metadata
   217  		{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v2"}, cfgs[2], false},                                                                      //23. does not match rule1 because tag value does not pass filter
   218  		{ObjectOpts{Name: "xyz/c3test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], true},                                      //24. matches rule 2 - DeleteMarker replication allowed by rule
   219  		{ObjectOpts{Name: "xyz/c3test", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], false},                   //25. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of DeleteMarker
   220  		{ObjectOpts{Name: "xyz/c3test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], false},                                       //26. matches rule 1 - DeleteReplication disallowed by rule for permanent delete of version
   221  		{ObjectOpts{Name: "abc/c3test"}, cfgs[2], false},                                                                                        //27. matches no rule because object prefix does not match
   222  
   223  		// using config 3 - has no overlapping rules
   224  		{ObjectOpts{Name: "xy/c4test", UserTags: "k1=v1"}, cfgs[3], true},                                                                       //28. matches rule 1 for replication of content/metadata
   225  		{ObjectOpts{Name: "xa/c4test", UserTags: "k1=v1"}, cfgs[3], false},                                                                      //29. no rule match object prefix not in rules
   226  		{ObjectOpts{Name: "xyz/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false},                                     //30. rule 1 not matched because of tags filter
   227  		{ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false},                  //31. matches rule 1 - DeleteMarker replication disallowed by rule
   228  		{ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], true}, //32. matches rule 1 - DeleteReplication allowed by rule for permanent delete of DeleteMarker
   229  		{ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], true},                     //33. matches rule 1 - DeleteReplication allowed by rule for permanent delete of version
   230  		{ObjectOpts{Name: "abc/c4test"}, cfgs[3], true},                                                                                         //34. matches rule 2 for replication of content/metadata
   231  		{ObjectOpts{Name: "abc/c4test", UserTags: "k1=v2"}, cfgs[3], true},                                                                      //35. matches rule 2 for replication of content/metadata
   232  		{ObjectOpts{Name: "abc/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], true},                                      //36. matches rule 2 - DeleteMarker replication allowed by rule
   233  		{ObjectOpts{Name: "abc/c4test", DeleteMarker: true, VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], false},                   //37. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of DeleteMarker
   234  		{ObjectOpts{Name: "abc/c4test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], false},                                       //38. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of version
   235  
   236  	}
   237  
   238  	for i, testCase := range testCases {
   239  		result := testCase.c.Replicate(testCase.opts)
   240  
   241  		if result != testCase.expectedResult {
   242  			t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
   243  		}
   244  	}
   245  }
   246  
   247  func TestHasActiveRules(t *testing.T) {
   248  	testCases := []struct {
   249  		inputConfig    string
   250  		prefix         string
   251  		expectedNonRec bool
   252  		expectedRec    bool
   253  	}{
   254  
   255  		// case 1 - only one rule which is in Disabled status
   256  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Disabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Prefix>key-prefix</Prefix><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
   257  			prefix:         "miss/prefix",
   258  			expectedNonRec: false,
   259  			expectedRec:    false,
   260  		},
   261  		// case 2 - only one rule which matches prefix filter
   262  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Filter><Prefix>key/prefix</Prefix></Filter><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
   263  			prefix:         "key/prefix1",
   264  			expectedNonRec: true,
   265  			expectedRec:    true,
   266  		},
   267  		// case 3 - empty prefix
   268  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
   269  			prefix:         "key-prefix",
   270  			expectedNonRec: true,
   271  			expectedRec:    true,
   272  		},
   273  		// case 4 - has Filter based on prefix
   274  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Filter><Prefix>testdir/dir1/</Prefix></Filter><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
   275  			prefix:         "testdir/",
   276  			expectedNonRec: false,
   277  			expectedRec:    true,
   278  		},
   279  		//case 5 - has filter with prefix and tags, here we are not matching on tags
   280  		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role><Rule><Status>Enabled</Status><DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication><DeleteReplication><Status>Disabled</Status></DeleteReplication><Filter>
   281  		<And><Prefix>key-prefix</Prefix><Tag><Key>key1</Key><Value>value1</Value></Tag><Tag><Key>key2</Key><Value>value2</Value></Tag></And></Filter><Destination><Bucket>arn:aws:s3:::destinationbucket</Bucket></Destination></Rule></ReplicationConfiguration>`,
   282  			prefix:         "testdir/",
   283  			expectedNonRec: true,
   284  			expectedRec:    true,
   285  		},
   286  	}
   287  
   288  	for i, tc := range testCases {
   289  		tc := tc
   290  		t.Run(fmt.Sprintf("Test_%d", i+1), func(t *testing.T) {
   291  			cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig)))
   292  			if err != nil {
   293  				t.Fatalf("Got unexpected error: %v", err)
   294  			}
   295  			if got := cfg.HasActiveRules(tc.prefix, false); got != tc.expectedNonRec {
   296  				t.Fatalf("Expected result with recursive set to false: `%v`, got: `%v`", tc.expectedNonRec, got)
   297  			}
   298  			if got := cfg.HasActiveRules(tc.prefix, true); got != tc.expectedRec {
   299  				t.Fatalf("Expected result with recursive set to true: `%v`, got: `%v`", tc.expectedRec, got)
   300  			}
   301  
   302  		})
   303  
   304  	}
   305  }