| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- 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)
- }
- }
|