wildcard_matcher_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. package policy_engine
  2. import (
  3. "testing"
  4. )
  5. func TestMatchesWildcard(t *testing.T) {
  6. tests := []struct {
  7. name string
  8. pattern string
  9. str string
  10. expected bool
  11. }{
  12. // Basic functionality tests
  13. {
  14. name: "Exact match",
  15. pattern: "test",
  16. str: "test",
  17. expected: true,
  18. },
  19. {
  20. name: "Single wildcard",
  21. pattern: "*",
  22. str: "anything",
  23. expected: true,
  24. },
  25. {
  26. name: "Empty string with wildcard",
  27. pattern: "*",
  28. str: "",
  29. expected: true,
  30. },
  31. // Star (*) wildcard tests
  32. {
  33. name: "Prefix wildcard",
  34. pattern: "test*",
  35. str: "test123",
  36. expected: true,
  37. },
  38. {
  39. name: "Suffix wildcard",
  40. pattern: "*test",
  41. str: "123test",
  42. expected: true,
  43. },
  44. {
  45. name: "Middle wildcard",
  46. pattern: "test*123",
  47. str: "testABC123",
  48. expected: true,
  49. },
  50. {
  51. name: "Multiple wildcards",
  52. pattern: "test*abc*123",
  53. str: "testXYZabcDEF123",
  54. expected: true,
  55. },
  56. {
  57. name: "No match",
  58. pattern: "test*",
  59. str: "other",
  60. expected: false,
  61. },
  62. // Question mark (?) wildcard tests
  63. {
  64. name: "Single question mark",
  65. pattern: "test?",
  66. str: "test1",
  67. expected: true,
  68. },
  69. {
  70. name: "Multiple question marks",
  71. pattern: "test??",
  72. str: "test12",
  73. expected: true,
  74. },
  75. {
  76. name: "Question mark no match",
  77. pattern: "test?",
  78. str: "test12",
  79. expected: false,
  80. },
  81. {
  82. name: "Mixed wildcards",
  83. pattern: "test*abc?def",
  84. str: "testXYZabc1def",
  85. expected: true,
  86. },
  87. // Edge cases
  88. {
  89. name: "Empty pattern",
  90. pattern: "",
  91. str: "",
  92. expected: true,
  93. },
  94. {
  95. name: "Empty pattern with string",
  96. pattern: "",
  97. str: "test",
  98. expected: false,
  99. },
  100. {
  101. name: "Pattern with string empty",
  102. pattern: "test",
  103. str: "",
  104. expected: false,
  105. },
  106. // Special characters
  107. {
  108. name: "Pattern with regex special chars",
  109. pattern: "test[abc]",
  110. str: "test[abc]",
  111. expected: true,
  112. },
  113. {
  114. name: "Pattern with dots",
  115. pattern: "test.txt",
  116. str: "test.txt",
  117. expected: true,
  118. },
  119. {
  120. name: "Pattern with dots and wildcard",
  121. pattern: "*.txt",
  122. str: "test.txt",
  123. expected: true,
  124. },
  125. }
  126. for _, tt := range tests {
  127. t.Run(tt.name, func(t *testing.T) {
  128. result := MatchesWildcard(tt.pattern, tt.str)
  129. if result != tt.expected {
  130. t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, tt.str, tt.expected, result)
  131. }
  132. })
  133. }
  134. }
  135. func TestWildcardMatcher(t *testing.T) {
  136. tests := []struct {
  137. name string
  138. pattern string
  139. strings []string
  140. expected []bool
  141. }{
  142. {
  143. name: "Simple star pattern",
  144. pattern: "test*",
  145. strings: []string{"test", "test123", "testing", "other"},
  146. expected: []bool{true, true, true, false},
  147. },
  148. {
  149. name: "Question mark pattern",
  150. pattern: "test?",
  151. strings: []string{"test1", "test2", "test", "test12"},
  152. expected: []bool{true, true, false, false},
  153. },
  154. {
  155. name: "Mixed pattern",
  156. pattern: "*.txt",
  157. strings: []string{"file.txt", "test.txt", "file.doc", "txt"},
  158. expected: []bool{true, true, false, false},
  159. },
  160. }
  161. for _, tt := range tests {
  162. t.Run(tt.name, func(t *testing.T) {
  163. matcher, err := NewWildcardMatcher(tt.pattern)
  164. if err != nil {
  165. t.Fatalf("Failed to create matcher: %v", err)
  166. }
  167. for i, str := range tt.strings {
  168. result := matcher.Match(str)
  169. if result != tt.expected[i] {
  170. t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, str, tt.expected[i], result)
  171. }
  172. }
  173. })
  174. }
  175. }
  176. func TestCompileWildcardPattern(t *testing.T) {
  177. tests := []struct {
  178. name string
  179. pattern string
  180. input string
  181. want bool
  182. }{
  183. {"Star wildcard", "s3:Get*", "s3:GetObject", true},
  184. {"Question mark wildcard", "s3:Get?bject", "s3:GetObject", true},
  185. {"Mixed wildcards", "s3:*Object*", "s3:GetObjectAcl", true},
  186. }
  187. for _, tt := range tests {
  188. t.Run(tt.name, func(t *testing.T) {
  189. regex, err := CompileWildcardPattern(tt.pattern)
  190. if err != nil {
  191. t.Errorf("CompileWildcardPattern() error = %v", err)
  192. return
  193. }
  194. got := regex.MatchString(tt.input)
  195. if got != tt.want {
  196. t.Errorf("CompileWildcardPattern() = %v, want %v", got, tt.want)
  197. }
  198. })
  199. }
  200. }
  201. // BenchmarkWildcardMatchingPerformance demonstrates the performance benefits of caching
  202. func BenchmarkWildcardMatchingPerformance(b *testing.B) {
  203. patterns := []string{
  204. "s3:Get*",
  205. "s3:Put*",
  206. "s3:Delete*",
  207. "s3:List*",
  208. "arn:aws:s3:::bucket/*",
  209. "arn:aws:s3:::bucket/prefix*",
  210. "user:*",
  211. "user:admin-*",
  212. }
  213. inputs := []string{
  214. "s3:GetObject",
  215. "s3:PutObject",
  216. "s3:DeleteObject",
  217. "s3:ListBucket",
  218. "arn:aws:s3:::bucket/file.txt",
  219. "arn:aws:s3:::bucket/prefix/file.txt",
  220. "user:admin",
  221. "user:admin-john",
  222. }
  223. b.Run("WithoutCache", func(b *testing.B) {
  224. for i := 0; i < b.N; i++ {
  225. for _, pattern := range patterns {
  226. for _, input := range inputs {
  227. MatchesWildcard(pattern, input)
  228. }
  229. }
  230. }
  231. })
  232. b.Run("WithCache", func(b *testing.B) {
  233. for i := 0; i < b.N; i++ {
  234. for _, pattern := range patterns {
  235. for _, input := range inputs {
  236. FastMatchesWildcard(pattern, input)
  237. }
  238. }
  239. }
  240. })
  241. }
  242. // BenchmarkWildcardMatcherReuse demonstrates the performance benefits of reusing WildcardMatcher instances
  243. func BenchmarkWildcardMatcherReuse(b *testing.B) {
  244. pattern := "s3:Get*"
  245. input := "s3:GetObject"
  246. b.Run("NewMatcherEveryTime", func(b *testing.B) {
  247. for i := 0; i < b.N; i++ {
  248. matcher, _ := NewWildcardMatcher(pattern)
  249. matcher.Match(input)
  250. }
  251. })
  252. b.Run("CachedMatcher", func(b *testing.B) {
  253. for i := 0; i < b.N; i++ {
  254. matcher, _ := GetCachedWildcardMatcher(pattern)
  255. matcher.Match(input)
  256. }
  257. })
  258. }
  259. // TestWildcardMatcherCaching verifies that caching works correctly
  260. func TestWildcardMatcherCaching(t *testing.T) {
  261. pattern := "s3:Get*"
  262. // Get the first matcher
  263. matcher1, err := GetCachedWildcardMatcher(pattern)
  264. if err != nil {
  265. t.Fatalf("Failed to get cached matcher: %v", err)
  266. }
  267. // Get the second matcher - should be the same instance
  268. matcher2, err := GetCachedWildcardMatcher(pattern)
  269. if err != nil {
  270. t.Fatalf("Failed to get cached matcher: %v", err)
  271. }
  272. // Check that they're the same instance (same pointer)
  273. if matcher1 != matcher2 {
  274. t.Errorf("Expected same matcher instance, got different instances")
  275. }
  276. // Test that both matchers work correctly
  277. testInput := "s3:GetObject"
  278. if !matcher1.Match(testInput) {
  279. t.Errorf("First matcher failed to match %s", testInput)
  280. }
  281. if !matcher2.Match(testInput) {
  282. t.Errorf("Second matcher failed to match %s", testInput)
  283. }
  284. }
  285. // TestFastMatchesWildcard verifies that the fast matching function works correctly
  286. func TestFastMatchesWildcard(t *testing.T) {
  287. tests := []struct {
  288. pattern string
  289. input string
  290. want bool
  291. }{
  292. {"s3:Get*", "s3:GetObject", true},
  293. {"s3:Put*", "s3:GetObject", false},
  294. {"arn:aws:s3:::bucket/*", "arn:aws:s3:::bucket/file.txt", true},
  295. {"user:admin-*", "user:admin-john", true},
  296. {"user:admin-*", "user:guest-john", false},
  297. }
  298. for _, tt := range tests {
  299. t.Run(tt.pattern+"_"+tt.input, func(t *testing.T) {
  300. got := FastMatchesWildcard(tt.pattern, tt.input)
  301. if got != tt.want {
  302. t.Errorf("FastMatchesWildcard(%q, %q) = %v, want %v", tt.pattern, tt.input, got, tt.want)
  303. }
  304. })
  305. }
  306. }
  307. // TestWildcardMatcherCacheBounding tests the bounded cache functionality
  308. func TestWildcardMatcherCacheBounding(t *testing.T) {
  309. // Clear cache before test
  310. wildcardMatcherCache.ClearCache()
  311. // Get original max size
  312. originalMaxSize := wildcardMatcherCache.maxSize
  313. // Set a small max size for testing
  314. wildcardMatcherCache.maxSize = 3
  315. defer func() {
  316. wildcardMatcherCache.maxSize = originalMaxSize
  317. wildcardMatcherCache.ClearCache()
  318. }()
  319. // Add patterns up to max size
  320. patterns := []string{"pattern1", "pattern2", "pattern3"}
  321. for _, pattern := range patterns {
  322. _, err := GetCachedWildcardMatcher(pattern)
  323. if err != nil {
  324. t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
  325. }
  326. }
  327. // Verify cache size
  328. size, maxSize := wildcardMatcherCache.GetCacheStats()
  329. if size != 3 {
  330. t.Errorf("Expected cache size 3, got %d", size)
  331. }
  332. if maxSize != 3 {
  333. t.Errorf("Expected max size 3, got %d", maxSize)
  334. }
  335. // Add another pattern, should evict the least recently used
  336. _, err := GetCachedWildcardMatcher("pattern4")
  337. if err != nil {
  338. t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
  339. }
  340. // Cache should still be at max size
  341. size, _ = wildcardMatcherCache.GetCacheStats()
  342. if size != 3 {
  343. t.Errorf("Expected cache size 3 after eviction, got %d", size)
  344. }
  345. // The first pattern should have been evicted
  346. wildcardMatcherCache.mu.RLock()
  347. if _, exists := wildcardMatcherCache.matchers["pattern1"]; exists {
  348. t.Errorf("Expected pattern1 to be evicted, but it still exists")
  349. }
  350. if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
  351. t.Errorf("Expected pattern4 to be in cache, but it doesn't exist")
  352. }
  353. wildcardMatcherCache.mu.RUnlock()
  354. }
  355. // TestWildcardMatcherCacheLRU tests the LRU eviction policy
  356. func TestWildcardMatcherCacheLRU(t *testing.T) {
  357. // Clear cache before test
  358. wildcardMatcherCache.ClearCache()
  359. // Get original max size
  360. originalMaxSize := wildcardMatcherCache.maxSize
  361. // Set a small max size for testing
  362. wildcardMatcherCache.maxSize = 3
  363. defer func() {
  364. wildcardMatcherCache.maxSize = originalMaxSize
  365. wildcardMatcherCache.ClearCache()
  366. }()
  367. // Add patterns to fill cache
  368. patterns := []string{"pattern1", "pattern2", "pattern3"}
  369. for _, pattern := range patterns {
  370. _, err := GetCachedWildcardMatcher(pattern)
  371. if err != nil {
  372. t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
  373. }
  374. }
  375. // Access pattern1 to make it most recently used
  376. _, err := GetCachedWildcardMatcher("pattern1")
  377. if err != nil {
  378. t.Fatalf("Failed to access pattern1: %v", err)
  379. }
  380. // Add another pattern, should evict pattern2 (now least recently used)
  381. _, err = GetCachedWildcardMatcher("pattern4")
  382. if err != nil {
  383. t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
  384. }
  385. // pattern1 should still be in cache (was accessed recently)
  386. // pattern2 should be evicted (was least recently used)
  387. wildcardMatcherCache.mu.RLock()
  388. if _, exists := wildcardMatcherCache.matchers["pattern1"]; !exists {
  389. t.Errorf("Expected pattern1 to remain in cache (most recently used)")
  390. }
  391. if _, exists := wildcardMatcherCache.matchers["pattern2"]; exists {
  392. t.Errorf("Expected pattern2 to be evicted (least recently used)")
  393. }
  394. if _, exists := wildcardMatcherCache.matchers["pattern3"]; !exists {
  395. t.Errorf("Expected pattern3 to remain in cache")
  396. }
  397. if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
  398. t.Errorf("Expected pattern4 to be in cache")
  399. }
  400. wildcardMatcherCache.mu.RUnlock()
  401. }
  402. // TestWildcardMatcherCacheClear tests the cache clearing functionality
  403. func TestWildcardMatcherCacheClear(t *testing.T) {
  404. // Add some patterns to cache
  405. patterns := []string{"pattern1", "pattern2", "pattern3"}
  406. for _, pattern := range patterns {
  407. _, err := GetCachedWildcardMatcher(pattern)
  408. if err != nil {
  409. t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
  410. }
  411. }
  412. // Verify cache has patterns
  413. size, _ := wildcardMatcherCache.GetCacheStats()
  414. if size == 0 {
  415. t.Errorf("Expected cache to have patterns before clearing")
  416. }
  417. // Clear cache
  418. wildcardMatcherCache.ClearCache()
  419. // Verify cache is empty
  420. size, _ = wildcardMatcherCache.GetCacheStats()
  421. if size != 0 {
  422. t.Errorf("Expected cache to be empty after clearing, got size %d", size)
  423. }
  424. }