package replication

import (
	"bytes"
	"fmt"
	"testing"
)

func TestParseAndValidateReplicationConfig(t *testing.T) {
	testCases := []struct {
		inputConfig           string
		expectedParsingErr    error
		expectedValidationErr error
		destBucket            string
		sameTarget            bool
	}{
		{ //1 Invalid delete marker status in replication config
			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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errInvalidDeleteMarkerReplicationStatus,
		},
		//2 Invalid delete replication status in replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errDeleteReplicationMissing,
		},
		//3 valid replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: nil,
		},
		//4 missing role in config
		{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>`,
			// destination bucket in config different from bucket specified
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errRoleArnMissing,
		},
		//5 replication destination in different rules not identical
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errReplicationDestinationMismatch,
		},
		//6 missing rule status in replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errEmptyRuleStatus,
		},
		//7 invalid rule status in replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errInvalidRuleStatus,
		},
		//8 invalid rule id exceeds length allowed in replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errInvalidRuleID,
		},
		//9 invalid priority status in replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errReplicationUniquePriority,
		},
		//10 no rule in replication config
		{inputConfig: `<ReplicationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Role>arn:aws:iam::AcctID:role/role-name</Role></ReplicationConfiguration>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    nil,
			expectedValidationErr: errReplicationNoRule,
		},
		//11 no destination in replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    Errorf("invalid destination '%v'", ""),
			expectedValidationErr: nil,
		},
		//12 destination not matching ARN in replication config
		{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>`,
			destBucket:            "destinationbucket",
			sameTarget:            false,
			expectedParsingErr:    fmt.Errorf("invalid destination '%v'", "destinationbucket2"),
			expectedValidationErr: nil,
		},
	}
	for i, tc := range testCases {
		t.Run(fmt.Sprintf("Test %d", i+1), func(t *testing.T) {
			cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig)))
			if err != nil && tc.expectedParsingErr != nil && err.Error() != tc.expectedParsingErr.Error() {
				t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err)
			}
			if err == nil && tc.expectedParsingErr != nil {
				t.Fatalf("%d: Expected '%v' during parsing but got '%v'", i+1, tc.expectedParsingErr, err)
			}
			if tc.expectedParsingErr != nil {
				// We already expect a parsing error,
				// no need to continue this test.
				return
			}
			err = cfg.Validate(tc.destBucket, tc.sameTarget)
			if err != tc.expectedValidationErr {
				t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedValidationErr, err)
			}
		})
	}
}
func TestReplicate(t *testing.T) {
	cfgs := []Config{
		{ //Config0 - Replication config has no filters, all replication enabled
			Rules: []Rule{
				{
					Status:                  Enabled,
					Priority:                3,
					DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
					DeleteReplication:       DeleteReplication{Status: Enabled},
					Filter:                  Filter{},
				},
			},
		},
		{ //Config1 - Replication config has no filters, delete,delete-marker replication disabled
			Rules: []Rule{
				{
					Status:                  Enabled,
					Priority:                3,
					DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
					DeleteReplication:       DeleteReplication{Status: Disabled},
					Filter:                  Filter{},
				},
			},
		},
		{ //Config2 - Replication config has filters and more than 1 matching rule, delete,delete-marker replication disabled
			Rules: []Rule{
				{
					Status:                  Enabled,
					Priority:                2,
					DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
					DeleteReplication:       DeleteReplication{Status: Enabled},
					Filter:                  Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}},
				},
				{
					Status:                  Enabled,
					Priority:                1,
					DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
					DeleteReplication:       DeleteReplication{Status: Disabled},
					Filter:                  Filter{Prefix: "xyz"},
				},
			},
		},
		{ //Config3 - Replication config has filters and no overlapping rules
			Rules: []Rule{
				{
					Status:                  Enabled,
					Priority:                2,
					DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
					DeleteReplication:       DeleteReplication{Status: Enabled},
					Filter:                  Filter{Prefix: "xy", And: And{}, Tag: Tag{Key: "k1", Value: "v1"}},
				},
				{
					Status:                  Enabled,
					Priority:                1,
					DeleteMarkerReplication: DeleteMarkerReplication{Status: Enabled},
					DeleteReplication:       DeleteReplication{Status: Disabled},
					Filter:                  Filter{Prefix: "abc"},
				},
			},
		},
	}
	testCases := []struct {
		opts           ObjectOpts
		c              Config
		expectedResult bool
	}{
		// using config 1 - no filters, all replication enabled
		{ObjectOpts{}, cfgs[0], false},                                //1. invalid ObjectOpts missing object name
		{ObjectOpts{Name: "c1test"}, cfgs[0], true},                   //2. valid ObjectOpts passing empty Filter
		{ObjectOpts{Name: "c1test", VersionID: "vid"}, cfgs[0], true}, //3. valid ObjectOpts passing empty Filter

		{ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true},                                //4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status
		{ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true},                                  //5. permanent delete of version, matches DeleteReplication status - valid case
		{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true},              //6. permanent delete of version, matches DeleteReplication status
		{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, //7. permanent delete of version, disqualified by SSE-C
		{ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false},                   //8. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C
		{ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], false},                                                                      //9. replication of SSE-C encrypted object, disqualified

		//  using config 2 - no filters, only replication of object, metadata enabled
		{ObjectOpts{Name: "c2test"}, cfgs[1], true},                                                                                   //10. valid ObjectOpts passing empty Filter
		{ObjectOpts{Name: "c2test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false},                               //11. DeleteMarker version replication  not allowed due to DeleteMarkerReplication status
		{ObjectOpts{Name: "c2test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[1], false},                                 //12. permanent delete of version, disallowed by DeleteReplication status
		{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false},             //13. permanent delete of DeleteMarker version, disallowed by DeleteReplication status
		{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
		{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
		{ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], false},                                                                      //16. replication of SSE-C encrypted object, disqualified by default
		// using config 2 - has more than one rule with overlapping prefixes
		{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v1"}, cfgs[2], true},                                                                       //17. matches rule 1 for replication of content/metadata
		{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1"}, cfgs[2], true},                                                                      //18. matches rule 1 for replication of content/metadata
		{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], false},                  //19. matches rule 1 - DeleteMarker replication disallowed by rule
		{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
		{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
		{ObjectOpts{Name: "xyz/c3test"}, cfgs[2], true},                                                                                         //22. matches rule 2 for replication of content/metadata
		{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v2"}, cfgs[2], false},                                                                      //23. does not match rule1 because tag value does not pass filter
		{ObjectOpts{Name: "xyz/c3test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[2], true},                                      //24. matches rule 2 - DeleteMarker replication allowed by rule
		{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
		{ObjectOpts{Name: "xyz/c3test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[2], false},                                       //26. matches rule 1 - DeleteReplication disallowed by rule for permanent delete of version
		{ObjectOpts{Name: "abc/c3test"}, cfgs[2], false},                                                                                        //27. matches no rule because object prefix does not match

		// using config 3 - has no overlapping rules
		{ObjectOpts{Name: "xy/c4test", UserTags: "k1=v1"}, cfgs[3], true},                                                                       //28. matches rule 1 for replication of content/metadata
		{ObjectOpts{Name: "xa/c4test", UserTags: "k1=v1"}, cfgs[3], false},                                                                      //29. no rule match object prefix not in rules
		{ObjectOpts{Name: "xyz/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false},                                     //30. rule 1 not matched because of tags filter
		{ObjectOpts{Name: "xyz/c4test", UserTags: "k1=v1", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], false},                  //31. matches rule 1 - DeleteMarker replication disallowed by rule
		{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
		{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
		{ObjectOpts{Name: "abc/c4test"}, cfgs[3], true},                                                                                         //34. matches rule 2 for replication of content/metadata
		{ObjectOpts{Name: "abc/c4test", UserTags: "k1=v2"}, cfgs[3], true},                                                                      //35. matches rule 2 for replication of content/metadata
		{ObjectOpts{Name: "abc/c4test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[3], true},                                      //36. matches rule 2 - DeleteMarker replication allowed by rule
		{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
		{ObjectOpts{Name: "abc/c4test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[3], false},                                       //38. matches rule 2 - DeleteReplication disallowed by rule for permanent delete of version

	}

	for i, testCase := range testCases {
		result := testCase.c.Replicate(testCase.opts)

		if result != testCase.expectedResult {
			t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
		}
	}
}

func TestHasActiveRules(t *testing.T) {
	testCases := []struct {
		inputConfig    string
		prefix         string
		expectedNonRec bool
		expectedRec    bool
	}{

		// case 1 - only one rule which is in Disabled status
		{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>`,
			prefix:         "miss/prefix",
			expectedNonRec: false,
			expectedRec:    false,
		},
		// case 2 - only one rule which matches prefix filter
		{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>`,
			prefix:         "key/prefix1",
			expectedNonRec: true,
			expectedRec:    true,
		},
		// case 3 - empty prefix
		{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>`,
			prefix:         "key-prefix",
			expectedNonRec: true,
			expectedRec:    true,
		},
		// case 4 - has Filter based on prefix
		{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>`,
			prefix:         "testdir/",
			expectedNonRec: false,
			expectedRec:    true,
		},
		//case 5 - has filter with prefix and tags, here we are not matching on tags
		{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>
		<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>`,
			prefix:         "testdir/",
			expectedNonRec: true,
			expectedRec:    true,
		},
	}

	for i, tc := range testCases {
		tc := tc
		t.Run(fmt.Sprintf("Test_%d", i+1), func(t *testing.T) {
			cfg, err := ParseConfig(bytes.NewReader([]byte(tc.inputConfig)))
			if err != nil {
				t.Fatalf("Got unexpected error: %v", err)
			}
			if got := cfg.HasActiveRules(tc.prefix, false); got != tc.expectedNonRec {
				t.Fatalf("Expected result with recursive set to false: `%v`, got: `%v`", tc.expectedNonRec, got)
			}
			if got := cfg.HasActiveRules(tc.prefix, true); got != tc.expectedRec {
				t.Fatalf("Expected result with recursive set to true: `%v`, got: `%v`", tc.expectedRec, got)
			}

		})

	}
}