You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							469 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							469 lines
						
					
					
						
							11 KiB
						
					
					
				
								package policy_engine
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"testing"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								func TestMatchesWildcard(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name     string
							 | 
						|
										pattern  string
							 | 
						|
										str      string
							 | 
						|
										expected bool
							 | 
						|
									}{
							 | 
						|
										// Basic functionality tests
							 | 
						|
										{
							 | 
						|
											name:     "Exact match",
							 | 
						|
											pattern:  "test",
							 | 
						|
											str:      "test",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Single wildcard",
							 | 
						|
											pattern:  "*",
							 | 
						|
											str:      "anything",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Empty string with wildcard",
							 | 
						|
											pattern:  "*",
							 | 
						|
											str:      "",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
								
							 | 
						|
										// Star (*) wildcard tests
							 | 
						|
										{
							 | 
						|
											name:     "Prefix wildcard",
							 | 
						|
											pattern:  "test*",
							 | 
						|
											str:      "test123",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Suffix wildcard",
							 | 
						|
											pattern:  "*test",
							 | 
						|
											str:      "123test",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Middle wildcard",
							 | 
						|
											pattern:  "test*123",
							 | 
						|
											str:      "testABC123",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Multiple wildcards",
							 | 
						|
											pattern:  "test*abc*123",
							 | 
						|
											str:      "testXYZabcDEF123",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "No match",
							 | 
						|
											pattern:  "test*",
							 | 
						|
											str:      "other",
							 | 
						|
											expected: false,
							 | 
						|
										},
							 | 
						|
								
							 | 
						|
										// Question mark (?) wildcard tests
							 | 
						|
										{
							 | 
						|
											name:     "Single question mark",
							 | 
						|
											pattern:  "test?",
							 | 
						|
											str:      "test1",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Multiple question marks",
							 | 
						|
											pattern:  "test??",
							 | 
						|
											str:      "test12",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Question mark no match",
							 | 
						|
											pattern:  "test?",
							 | 
						|
											str:      "test12",
							 | 
						|
											expected: false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Mixed wildcards",
							 | 
						|
											pattern:  "test*abc?def",
							 | 
						|
											str:      "testXYZabc1def",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
								
							 | 
						|
										// Edge cases
							 | 
						|
										{
							 | 
						|
											name:     "Empty pattern",
							 | 
						|
											pattern:  "",
							 | 
						|
											str:      "",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Empty pattern with string",
							 | 
						|
											pattern:  "",
							 | 
						|
											str:      "test",
							 | 
						|
											expected: false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Pattern with string empty",
							 | 
						|
											pattern:  "test",
							 | 
						|
											str:      "",
							 | 
						|
											expected: false,
							 | 
						|
										},
							 | 
						|
								
							 | 
						|
										// Special characters
							 | 
						|
										{
							 | 
						|
											name:     "Pattern with regex special chars",
							 | 
						|
											pattern:  "test[abc]",
							 | 
						|
											str:      "test[abc]",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Pattern with dots",
							 | 
						|
											pattern:  "test.txt",
							 | 
						|
											str:      "test.txt",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Pattern with dots and wildcard",
							 | 
						|
											pattern:  "*.txt",
							 | 
						|
											str:      "test.txt",
							 | 
						|
											expected: true,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											result := MatchesWildcard(tt.pattern, tt.str)
							 | 
						|
											if result != tt.expected {
							 | 
						|
												t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, tt.str, tt.expected, result)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func TestWildcardMatcher(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name     string
							 | 
						|
										pattern  string
							 | 
						|
										strings  []string
							 | 
						|
										expected []bool
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:     "Simple star pattern",
							 | 
						|
											pattern:  "test*",
							 | 
						|
											strings:  []string{"test", "test123", "testing", "other"},
							 | 
						|
											expected: []bool{true, true, true, false},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Question mark pattern",
							 | 
						|
											pattern:  "test?",
							 | 
						|
											strings:  []string{"test1", "test2", "test", "test12"},
							 | 
						|
											expected: []bool{true, true, false, false},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "Mixed pattern",
							 | 
						|
											pattern:  "*.txt",
							 | 
						|
											strings:  []string{"file.txt", "test.txt", "file.doc", "txt"},
							 | 
						|
											expected: []bool{true, true, false, false},
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											matcher, err := NewWildcardMatcher(tt.pattern)
							 | 
						|
											if err != nil {
							 | 
						|
												t.Fatalf("Failed to create matcher: %v", err)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											for i, str := range tt.strings {
							 | 
						|
												result := matcher.Match(str)
							 | 
						|
												if result != tt.expected[i] {
							 | 
						|
													t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, str, tt.expected[i], result)
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func TestCompileWildcardPattern(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name    string
							 | 
						|
										pattern string
							 | 
						|
										input   string
							 | 
						|
										want    bool
							 | 
						|
									}{
							 | 
						|
										{"Star wildcard", "s3:Get*", "s3:GetObject", true},
							 | 
						|
										{"Question mark wildcard", "s3:Get?bject", "s3:GetObject", true},
							 | 
						|
										{"Mixed wildcards", "s3:*Object*", "s3:GetObjectAcl", true},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											regex, err := CompileWildcardPattern(tt.pattern)
							 | 
						|
											if err != nil {
							 | 
						|
												t.Errorf("CompileWildcardPattern() error = %v", err)
							 | 
						|
												return
							 | 
						|
											}
							 | 
						|
											got := regex.MatchString(tt.input)
							 | 
						|
											if got != tt.want {
							 | 
						|
												t.Errorf("CompileWildcardPattern() = %v, want %v", got, tt.want)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// BenchmarkWildcardMatchingPerformance demonstrates the performance benefits of caching
							 | 
						|
								func BenchmarkWildcardMatchingPerformance(b *testing.B) {
							 | 
						|
									patterns := []string{
							 | 
						|
										"s3:Get*",
							 | 
						|
										"s3:Put*",
							 | 
						|
										"s3:Delete*",
							 | 
						|
										"s3:List*",
							 | 
						|
										"arn:aws:s3:::bucket/*",
							 | 
						|
										"arn:aws:s3:::bucket/prefix*",
							 | 
						|
										"user:*",
							 | 
						|
										"user:admin-*",
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									inputs := []string{
							 | 
						|
										"s3:GetObject",
							 | 
						|
										"s3:PutObject",
							 | 
						|
										"s3:DeleteObject",
							 | 
						|
										"s3:ListBucket",
							 | 
						|
										"arn:aws:s3:::bucket/file.txt",
							 | 
						|
										"arn:aws:s3:::bucket/prefix/file.txt",
							 | 
						|
										"user:admin",
							 | 
						|
										"user:admin-john",
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									b.Run("WithoutCache", func(b *testing.B) {
							 | 
						|
										for i := 0; i < b.N; i++ {
							 | 
						|
											for _, pattern := range patterns {
							 | 
						|
												for _, input := range inputs {
							 | 
						|
													MatchesWildcard(pattern, input)
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									b.Run("WithCache", func(b *testing.B) {
							 | 
						|
										for i := 0; i < b.N; i++ {
							 | 
						|
											for _, pattern := range patterns {
							 | 
						|
												for _, input := range inputs {
							 | 
						|
													FastMatchesWildcard(pattern, input)
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// BenchmarkWildcardMatcherReuse demonstrates the performance benefits of reusing WildcardMatcher instances
							 | 
						|
								func BenchmarkWildcardMatcherReuse(b *testing.B) {
							 | 
						|
									pattern := "s3:Get*"
							 | 
						|
									input := "s3:GetObject"
							 | 
						|
								
							 | 
						|
									b.Run("NewMatcherEveryTime", func(b *testing.B) {
							 | 
						|
										for i := 0; i < b.N; i++ {
							 | 
						|
											matcher, _ := NewWildcardMatcher(pattern)
							 | 
						|
											matcher.Match(input)
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									b.Run("CachedMatcher", func(b *testing.B) {
							 | 
						|
										for i := 0; i < b.N; i++ {
							 | 
						|
											matcher, _ := GetCachedWildcardMatcher(pattern)
							 | 
						|
											matcher.Match(input)
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestWildcardMatcherCaching verifies that caching works correctly
							 | 
						|
								func TestWildcardMatcherCaching(t *testing.T) {
							 | 
						|
									pattern := "s3:Get*"
							 | 
						|
								
							 | 
						|
									// Get the first matcher
							 | 
						|
									matcher1, err := GetCachedWildcardMatcher(pattern)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to get cached matcher: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Get the second matcher - should be the same instance
							 | 
						|
									matcher2, err := GetCachedWildcardMatcher(pattern)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to get cached matcher: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check that they're the same instance (same pointer)
							 | 
						|
									if matcher1 != matcher2 {
							 | 
						|
										t.Errorf("Expected same matcher instance, got different instances")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Test that both matchers work correctly
							 | 
						|
									testInput := "s3:GetObject"
							 | 
						|
									if !matcher1.Match(testInput) {
							 | 
						|
										t.Errorf("First matcher failed to match %s", testInput)
							 | 
						|
									}
							 | 
						|
									if !matcher2.Match(testInput) {
							 | 
						|
										t.Errorf("Second matcher failed to match %s", testInput)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestFastMatchesWildcard verifies that the fast matching function works correctly
							 | 
						|
								func TestFastMatchesWildcard(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										pattern string
							 | 
						|
										input   string
							 | 
						|
										want    bool
							 | 
						|
									}{
							 | 
						|
										{"s3:Get*", "s3:GetObject", true},
							 | 
						|
										{"s3:Put*", "s3:GetObject", false},
							 | 
						|
										{"arn:aws:s3:::bucket/*", "arn:aws:s3:::bucket/file.txt", true},
							 | 
						|
										{"user:admin-*", "user:admin-john", true},
							 | 
						|
										{"user:admin-*", "user:guest-john", false},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.pattern+"_"+tt.input, func(t *testing.T) {
							 | 
						|
											got := FastMatchesWildcard(tt.pattern, tt.input)
							 | 
						|
											if got != tt.want {
							 | 
						|
												t.Errorf("FastMatchesWildcard(%q, %q) = %v, want %v", tt.pattern, tt.input, got, tt.want)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestWildcardMatcherCacheBounding tests the bounded cache functionality
							 | 
						|
								func TestWildcardMatcherCacheBounding(t *testing.T) {
							 | 
						|
									// Clear cache before test
							 | 
						|
									wildcardMatcherCache.ClearCache()
							 | 
						|
								
							 | 
						|
									// Get original max size
							 | 
						|
									originalMaxSize := wildcardMatcherCache.maxSize
							 | 
						|
								
							 | 
						|
									// Set a small max size for testing
							 | 
						|
									wildcardMatcherCache.maxSize = 3
							 | 
						|
									defer func() {
							 | 
						|
										wildcardMatcherCache.maxSize = originalMaxSize
							 | 
						|
										wildcardMatcherCache.ClearCache()
							 | 
						|
									}()
							 | 
						|
								
							 | 
						|
									// Add patterns up to max size
							 | 
						|
									patterns := []string{"pattern1", "pattern2", "pattern3"}
							 | 
						|
									for _, pattern := range patterns {
							 | 
						|
										_, err := GetCachedWildcardMatcher(pattern)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Verify cache size
							 | 
						|
									size, maxSize := wildcardMatcherCache.GetCacheStats()
							 | 
						|
									if size != 3 {
							 | 
						|
										t.Errorf("Expected cache size 3, got %d", size)
							 | 
						|
									}
							 | 
						|
									if maxSize != 3 {
							 | 
						|
										t.Errorf("Expected max size 3, got %d", maxSize)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Add another pattern, should evict the least recently used
							 | 
						|
									_, err := GetCachedWildcardMatcher("pattern4")
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Cache should still be at max size
							 | 
						|
									size, _ = wildcardMatcherCache.GetCacheStats()
							 | 
						|
									if size != 3 {
							 | 
						|
										t.Errorf("Expected cache size 3 after eviction, got %d", size)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// The first pattern should have been evicted
							 | 
						|
									wildcardMatcherCache.mu.RLock()
							 | 
						|
									if _, exists := wildcardMatcherCache.matchers["pattern1"]; exists {
							 | 
						|
										t.Errorf("Expected pattern1 to be evicted, but it still exists")
							 | 
						|
									}
							 | 
						|
									if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
							 | 
						|
										t.Errorf("Expected pattern4 to be in cache, but it doesn't exist")
							 | 
						|
									}
							 | 
						|
									wildcardMatcherCache.mu.RUnlock()
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestWildcardMatcherCacheLRU tests the LRU eviction policy
							 | 
						|
								func TestWildcardMatcherCacheLRU(t *testing.T) {
							 | 
						|
									// Clear cache before test
							 | 
						|
									wildcardMatcherCache.ClearCache()
							 | 
						|
								
							 | 
						|
									// Get original max size
							 | 
						|
									originalMaxSize := wildcardMatcherCache.maxSize
							 | 
						|
								
							 | 
						|
									// Set a small max size for testing
							 | 
						|
									wildcardMatcherCache.maxSize = 3
							 | 
						|
									defer func() {
							 | 
						|
										wildcardMatcherCache.maxSize = originalMaxSize
							 | 
						|
										wildcardMatcherCache.ClearCache()
							 | 
						|
									}()
							 | 
						|
								
							 | 
						|
									// Add patterns to fill cache
							 | 
						|
									patterns := []string{"pattern1", "pattern2", "pattern3"}
							 | 
						|
									for _, pattern := range patterns {
							 | 
						|
										_, err := GetCachedWildcardMatcher(pattern)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Access pattern1 to make it most recently used
							 | 
						|
									_, err := GetCachedWildcardMatcher("pattern1")
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to access pattern1: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Add another pattern, should evict pattern2 (now least recently used)
							 | 
						|
									_, err = GetCachedWildcardMatcher("pattern4")
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// pattern1 should still be in cache (was accessed recently)
							 | 
						|
									// pattern2 should be evicted (was least recently used)
							 | 
						|
									wildcardMatcherCache.mu.RLock()
							 | 
						|
									if _, exists := wildcardMatcherCache.matchers["pattern1"]; !exists {
							 | 
						|
										t.Errorf("Expected pattern1 to remain in cache (most recently used)")
							 | 
						|
									}
							 | 
						|
									if _, exists := wildcardMatcherCache.matchers["pattern2"]; exists {
							 | 
						|
										t.Errorf("Expected pattern2 to be evicted (least recently used)")
							 | 
						|
									}
							 | 
						|
									if _, exists := wildcardMatcherCache.matchers["pattern3"]; !exists {
							 | 
						|
										t.Errorf("Expected pattern3 to remain in cache")
							 | 
						|
									}
							 | 
						|
									if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
							 | 
						|
										t.Errorf("Expected pattern4 to be in cache")
							 | 
						|
									}
							 | 
						|
									wildcardMatcherCache.mu.RUnlock()
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestWildcardMatcherCacheClear tests the cache clearing functionality
							 | 
						|
								func TestWildcardMatcherCacheClear(t *testing.T) {
							 | 
						|
									// Add some patterns to cache
							 | 
						|
									patterns := []string{"pattern1", "pattern2", "pattern3"}
							 | 
						|
									for _, pattern := range patterns {
							 | 
						|
										_, err := GetCachedWildcardMatcher(pattern)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Verify cache has patterns
							 | 
						|
									size, _ := wildcardMatcherCache.GetCacheStats()
							 | 
						|
									if size == 0 {
							 | 
						|
										t.Errorf("Expected cache to have patterns before clearing")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Clear cache
							 | 
						|
									wildcardMatcherCache.ClearCache()
							 | 
						|
								
							 | 
						|
									// Verify cache is empty
							 | 
						|
									size, _ = wildcardMatcherCache.GetCacheStats()
							 | 
						|
									if size != 0 {
							 | 
						|
										t.Errorf("Expected cache to be empty after clearing, got size %d", size)
							 | 
						|
									}
							 | 
						|
								}
							 |