From 58eff8376ecbc8015985d4a16049325d3ff3a8d0 Mon Sep 17 00:00:00 2001 From: Thomas CAI <92149044+ThomasCAI-mlv@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:19:47 +0200 Subject: [PATCH] Handle wildcard parameter in Streams list API (#411) --- .../ns4kafka/controller/StreamController.java | 11 +- .../ns4kafka/service/StreamService.java | 18 +- .../michelin/ns4kafka/util/RegexUtils.java | 40 ++++ .../controller/StreamControllerTest.java | 108 ++++++++--- .../ns4kafka/service/StreamServiceTest.java | 178 +++++++++++++++--- .../ns4kafka/util/RegexUtilsTest.java | 159 ++++++++++++++++ 6 files changed, 456 insertions(+), 58 deletions(-) create mode 100644 src/main/java/com/michelin/ns4kafka/util/RegexUtils.java create mode 100644 src/test/java/com/michelin/ns4kafka/util/RegexUtilsTest.java diff --git a/src/main/java/com/michelin/ns4kafka/controller/StreamController.java b/src/main/java/com/michelin/ns4kafka/controller/StreamController.java index 187e4ffb..d8c17001 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/StreamController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/StreamController.java @@ -36,14 +36,15 @@ public class StreamController extends NamespacedResourceController { StreamService streamService; /** - * List Kafka Streams by namespace. + * List Kafka Streams by namespace, filtered by name parameter. * * @param namespace The namespace + * @param name The name parameter * @return A list of Kafka Streams */ - @Get("/") - List list(String namespace) { - return streamService.findAllForNamespace(getNamespace(namespace)); + @Get + List list(String namespace, @QueryValue(defaultValue = "*") String name) { + return streamService.findByWildcardName(getNamespace(namespace), name); } /** @@ -52,8 +53,10 @@ List list(String namespace) { * @param namespace The name * @param stream The Kafka Streams name * @return The Kafka Streams + * @deprecated Use ${@link #list(String, String)} */ @Get("/{stream}") + @Deprecated(since = "1.12.0") Optional get(String namespace, String stream) { return streamService.findByName(getNamespace(namespace), stream); } diff --git a/src/main/java/com/michelin/ns4kafka/service/StreamService.java b/src/main/java/com/michelin/ns4kafka/service/StreamService.java index 4e9ceaed..775c14fd 100644 --- a/src/main/java/com/michelin/ns4kafka/service/StreamService.java +++ b/src/main/java/com/michelin/ns4kafka/service/StreamService.java @@ -5,6 +5,7 @@ import com.michelin.ns4kafka.model.Namespace; import com.michelin.ns4kafka.repository.StreamRepository; import com.michelin.ns4kafka.service.executor.AccessControlEntryAsyncExecutor; +import com.michelin.ns4kafka.util.RegexUtils; import io.micronaut.context.ApplicationContext; import io.micronaut.inject.qualifiers.Qualifiers; import jakarta.inject.Inject; @@ -28,7 +29,7 @@ public class StreamService { ApplicationContext applicationContext; /** - * Find all Kafka Streams by given namespace. + * Find all Kafka Streams of a given namespace. * * @param namespace The namespace * @return A list of Kafka Streams @@ -39,6 +40,21 @@ public List findAllForNamespace(Namespace namespace) { .toList(); } + /** + * Find all Kafka Streams of a given namespace, filtered by name parameter. + * + * @param namespace The namespace + * @param name The name filter + * @return A list of Kafka Streams + */ + public List findByWildcardName(Namespace namespace, String name) { + List nameFilterPatterns = RegexUtils.wildcardStringsToRegexPatterns(List.of(name)); + return findAllForNamespace(namespace) + .stream() + .filter(stream -> RegexUtils.filterByPattern(stream.getMetadata().getName(), nameFilterPatterns)) + .toList(); + } + /** * Find a Kafka Streams by namespace and name. * diff --git a/src/main/java/com/michelin/ns4kafka/util/RegexUtils.java b/src/main/java/com/michelin/ns4kafka/util/RegexUtils.java new file mode 100644 index 00000000..f5fdb8ce --- /dev/null +++ b/src/main/java/com/michelin/ns4kafka/util/RegexUtils.java @@ -0,0 +1,40 @@ +package com.michelin.ns4kafka.util; + +import java.util.List; +import java.util.regex.Pattern; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * Regex utils. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class RegexUtils { + /** + * Convert wildcard strings list to regex patterns list. + * + * @param wildcardStrings The wildcard strings + * @return A list of regex patterns + */ + public static List wildcardStringsToRegexPatterns(List wildcardStrings) { + return wildcardStrings.stream() + .map(wildcardString -> "^" + wildcardString + .replace(".", "\\.") + .replace("*", ".*") + .replace("?", ".") + .replaceAll("^$", ".*") + "$") + .toList(); + } + + /** + * Check if a string matches any pattern of a given list. + * + * @param resourceName The string + * @param regexPatterns The regex patterns + * @return true if any regex pattern matches the resourceName, false otherwise + */ + public static boolean filterByPattern(String resourceName, List regexPatterns) { + return regexPatterns.stream() + .anyMatch(pattern -> Pattern.compile(pattern).matcher(resourceName).matches()); + } +} diff --git a/src/test/java/com/michelin/ns4kafka/controller/StreamControllerTest.java b/src/test/java/com/michelin/ns4kafka/controller/StreamControllerTest.java index 6b18258c..71d8c649 100644 --- a/src/test/java/com/michelin/ns4kafka/controller/StreamControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controller/StreamControllerTest.java @@ -46,24 +46,25 @@ class StreamControllerTest { StreamController streamController; @Test - void listEmptyStreams() { + void shouldListStreamsWhenEmpty() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") .cluster("local") .build()) .build(); + when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); - when(streamService.findAllForNamespace(ns)) + when(streamService.findByWildcardName(ns, "*")) .thenReturn(List.of()); - List actual = streamController.list("test"); + List actual = streamController.list("test", "*"); assertEquals(0, actual.size()); } @Test - void listStreams() { + void shouldListStreamsWithWildcardParameter() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -85,17 +86,77 @@ void listStreams() { when(namespaceService.findByName("test")) .thenReturn(Optional.of(ns)); - when(streamService.findAllForNamespace(ns)) + when(streamService.findByWildcardName(ns, "*")) .thenReturn(List.of(stream1, stream2)); - List actual = streamController.list("test"); + List actual = streamController.list("test", "*"); assertEquals(2, actual.size()); assertTrue(actual.contains(stream1)); assertTrue(actual.contains(stream2)); } @Test - void getEmpty() { + void shouldListStreamsWithNamedParameter() { + Namespace ns = Namespace.builder() + .metadata(Metadata.builder() + .name("test") + .cluster("local") + .build()) + .build(); + + KafkaStream stream1 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("prefix.s1") + .build()) + .build(); + + when(namespaceService.findByName("test")) + .thenReturn(Optional.of(ns)); + when(streamService.findByWildcardName(ns, "prefix.s1")) + .thenReturn(List.of(stream1)); + + List actual = streamController.list("test", "prefix.s1"); + + assertEquals(1, actual.size()); + assertEquals("prefix.s1", actual.getFirst().getMetadata().getName()); + } + + @Test + void shouldListStreamsWithEmptyNameParameter() { + Namespace ns = Namespace.builder() + .metadata(Metadata.builder() + .name("test") + .cluster("local") + .build()) + .build(); + + KafkaStream stream1 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("prefix.s1") + .build()) + .build(); + + KafkaStream stream2 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("prefix.s2") + .build()) + .build(); + + when(namespaceService.findByName("test")) + .thenReturn(Optional.of(ns)); + when(streamService.findByWildcardName(ns, "")) + .thenReturn(List.of(stream1, stream2)); + + List actual = streamController.list("test", ""); + + assertEquals(2, actual.size()); + assertEquals("prefix.s1", actual.get(0).getMetadata().getName()); + assertEquals("prefix.s2", actual.get(1).getMetadata().getName()); + } + + @Test + @SuppressWarnings("deprecation") + void shouldGetStreamsWhenEmpty() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -114,7 +175,8 @@ void getEmpty() { } @Test - void getStreamFound() { + @SuppressWarnings("deprecation") + void shouldGetStreams() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -140,7 +202,7 @@ void getStreamFound() { } @Test - void createStreamSuccess() { + void shouldCreateStreams() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -162,8 +224,10 @@ void createStreamSuccess() { when(streamService.findByName(ns, "test_stream1")) .thenReturn(Optional.empty()); - when(securityService.username()).thenReturn(Optional.of("test-user")); - when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false); + when(securityService.username()) + .thenReturn(Optional.of("test-user")); + when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)) + .thenReturn(false); doNothing().when(applicationEventPublisher).publishEvent(any()); when(streamService.create(stream1)) @@ -176,7 +240,7 @@ void createStreamSuccess() { } @Test - void createStreamSuccessDryRun() { + void shouldCreateStreamsInDryRunMode() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -207,7 +271,7 @@ void createStreamSuccessDryRun() { } @Test - void updateStreamUnchanged() { + void shouldUpdateStreamsUnchanged() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -238,7 +302,7 @@ void updateStreamUnchanged() { } @Test - void createStreamValidationError() { + void shouldNotCreateStreamsWhenValidationErrors() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -263,7 +327,7 @@ void createStreamValidationError() { } @Test - void deleteStreamSuccess() { + void shouldDeleteStreams() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -271,7 +335,7 @@ void deleteStreamSuccess() { .build()) .build(); - KafkaStream stream1 = KafkaStream.builder() + KafkaStream stream = KafkaStream.builder() .metadata(Metadata.builder() .name("test_stream1") .build()) @@ -284,18 +348,18 @@ void deleteStreamSuccess() { .thenReturn(true); when(streamService.findByName(ns, "test_stream1")) - .thenReturn(Optional.of(stream1)); + .thenReturn(Optional.of(stream)); when(securityService.username()).thenReturn(Optional.of("test-user")); when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false); doNothing().when(applicationEventPublisher).publishEvent(any()); - doNothing().when(streamService).delete(ns, stream1); + doNothing().when(streamService).delete(ns, stream); var response = streamController.delete("test", "test_stream1", false); assertEquals(HttpStatus.NO_CONTENT, response.getStatus()); } @Test - void deleteStreamSuccessDryRun() { + void shouldDeleteStreamsInDryRunMode() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -324,7 +388,7 @@ void deleteStreamSuccessDryRun() { } @Test - void deleteStreamNotFound() { + void shouldNotDeleteStreamsWhenNotFound() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -347,7 +411,7 @@ void deleteStreamNotFound() { } @Test - void deleteStreamNotOwner() { + void shouldNotDeleteStreamsWhenNotOwner() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -364,4 +428,4 @@ void deleteStreamNotOwner() { assertThrows(ResourceValidationException.class, () -> streamController.delete("test", "test_stream1", false)); verify(streamService, never()).delete(any(), any()); } -} +} \ No newline at end of file diff --git a/src/test/java/com/michelin/ns4kafka/service/StreamServiceTest.java b/src/test/java/com/michelin/ns4kafka/service/StreamServiceTest.java index a4f3c20e..cad17c77 100644 --- a/src/test/java/com/michelin/ns4kafka/service/StreamServiceTest.java +++ b/src/test/java/com/michelin/ns4kafka/service/StreamServiceTest.java @@ -1,6 +1,7 @@ package com.michelin.ns4kafka.service; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @@ -10,7 +11,6 @@ import com.michelin.ns4kafka.model.Namespace; import com.michelin.ns4kafka.repository.StreamRepository; import java.util.List; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -29,7 +29,7 @@ class StreamServiceTest { StreamRepository streamRepository; @Test - void findAllEmpty() { + void shouldFindAllForClusterWhenEmpty() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -39,12 +39,13 @@ void findAllEmpty() { when(streamRepository.findAllForCluster("local")) .thenReturn(List.of()); + var actual = streamService.findAllForNamespace(ns); assertTrue(actual.isEmpty()); } @Test - void findAll() { + void shouldFindAllForCluster() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -76,7 +77,9 @@ void findAll() { when(streamRepository.findAllForCluster("local")) .thenReturn(List.of(stream1, stream2, stream3)); + var actual = streamService.findAllForNamespace(ns); + assertEquals(3, actual.size()); assertTrue(actual.contains(stream1)); assertTrue(actual.contains(stream2)); @@ -84,13 +87,60 @@ void findAll() { } @Test - void findByName() { + void shouldFindAllForNamespaceWithParameter() { + Namespace ns = Namespace.builder() + .metadata(Metadata.builder() + .name("test") + .cluster("local") + .build()) + .build(); + + KafkaStream stream1 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("test_stream1") + .namespace("test") + .cluster("local") + .build()) + .build(); + + KafkaStream stream2 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("test_stream2") + .namespace("test") + .cluster("local") + .build()) + .build(); + + KafkaStream stream3 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("test_stream3") + .namespace("test") + .cluster("local") + .build()) + .build(); + + when(streamRepository.findAllForCluster("local")) + .thenReturn(List.of(stream1, stream2, stream3)); + + List list1 = streamService.findByWildcardName(ns, "test_stream3"); + assertEquals(List.of(stream3), list1); + + List list2 = streamService.findByWildcardName(ns, "test_stream5"); + assertTrue(list2.isEmpty()); + + List list3 = streamService.findByWildcardName(ns, ""); + assertEquals(List.of(stream1, stream2, stream3), list3); + } + + @Test + void shouldFindAllForNamespaceWithWildcardParameter() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") .cluster("local") .build()) .build(); + KafkaStream stream1 = KafkaStream.builder() .metadata(Metadata.builder() .name("test_stream1") @@ -98,6 +148,7 @@ void findByName() { .cluster("local") .build()) .build(); + KafkaStream stream2 = KafkaStream.builder() .metadata(Metadata.builder() .name("test_stream2") @@ -105,6 +156,7 @@ void findByName() { .cluster("local") .build()) .build(); + KafkaStream stream3 = KafkaStream.builder() .metadata(Metadata.builder() .name("test_stream3") @@ -113,17 +165,85 @@ void findByName() { .build()) .build(); + KafkaStream stream4 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("test.stream1") + .namespace("test") + .cluster("local") + .build()) + .build(); + + KafkaStream stream5 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("stream2_test") + .namespace("test") + .cluster("local") + .build()) + .build(); + + KafkaStream stream6 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("prefix.stream_test1") + .namespace("test2") + .cluster("local") + .build()) + .build(); + + when(streamRepository.findAllForCluster("local")) + .thenReturn(List.of(stream1, stream2, stream3, stream4, stream5, stream6)); + + assertEquals(List.of(stream1, stream2, stream3), streamService.findByWildcardName(ns, "test_*")); + assertEquals(List.of(stream1, stream2, stream3), streamService.findByWildcardName(ns, "test_stream?")); + assertEquals(List.of(stream1, stream2, stream3, stream5), streamService.findByWildcardName(ns, "*_*")); + assertEquals(List.of(stream1, stream4), streamService.findByWildcardName(ns, "test?stream1")); + assertEquals(List.of(stream2, stream5), streamService.findByWildcardName(ns, "*stream2*")); + assertTrue(streamService.findByWildcardName(ns, "*stream5").isEmpty()); + assertTrue(streamService.findByWildcardName(ns, "test??stream1").isEmpty()); + assertTrue(streamService.findByWildcardName(ns, ".*").isEmpty()); + assertEquals(List.of(stream1, stream2, stream3, stream4, stream5), streamService.findByWildcardName(ns, "*")); + } + + @Test + void shouldFindByName() { + Namespace ns = Namespace.builder() + .metadata(Metadata.builder() + .name("test") + .cluster("local") + .build()) + .build(); + KafkaStream stream1 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("test_stream1") + .namespace("test") + .cluster("local") + .build()) + .build(); + KafkaStream stream2 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("test_stream2") + .namespace("test") + .cluster("local") + .build()) + .build(); + KafkaStream stream3 = KafkaStream.builder() + .metadata(Metadata.builder() + .name("test_stream3") + .namespace("test") + .cluster("local") + .build()) + .build(); when(streamRepository.findAllForCluster("local")) .thenReturn(List.of(stream1, stream2, stream3)); + var actual = streamService.findByName(ns, "test_stream2"); + assertTrue(actual.isPresent()); assertEquals(stream2, actual.get()); } @Test - void findByNameEmpty() { - + void shouldFindByNameWhenEmpty() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -133,12 +253,14 @@ void findByNameEmpty() { when(streamRepository.findAllForCluster("local")) .thenReturn(List.of()); + var actual = streamService.findByName(ns, "test_stream2"); + assertTrue(actual.isEmpty()); } @Test - void isNamespaceOwnerOfKafkaStream() { + void shouldNamespaceBeOwnerOfStreams() { Namespace ns = Namespace.builder() .metadata(Metadata.builder() .name("test") @@ -153,9 +275,9 @@ void isNamespaceOwnerOfKafkaStream() { .permission(AccessControlEntry.Permission.OWNER) .resource("test.") .grantedTo("test") - .build() - ) + .build()) .build(); + AccessControlEntry ace2 = AccessControlEntry.builder() .spec(AccessControlEntry.AccessControlEntrySpec.builder() .resourceType(AccessControlEntry.ResourceType.GROUP) @@ -163,9 +285,9 @@ void isNamespaceOwnerOfKafkaStream() { .permission(AccessControlEntry.Permission.OWNER) .resource("test.") .grantedTo("test") - .build() - ) + .build()) .build(); + AccessControlEntry ace3 = AccessControlEntry.builder() .spec(AccessControlEntry.AccessControlEntrySpec.builder() .resourceType(AccessControlEntry.ResourceType.CONNECT) @@ -173,9 +295,9 @@ void isNamespaceOwnerOfKafkaStream() { .permission(AccessControlEntry.Permission.OWNER) .resource("test.") .grantedTo("test") - .build() - ) + .build()) .build(); + AccessControlEntry ace4 = AccessControlEntry.builder() .spec(AccessControlEntry.AccessControlEntrySpec.builder() .resourceType(AccessControlEntry.ResourceType.TOPIC) @@ -183,9 +305,9 @@ void isNamespaceOwnerOfKafkaStream() { .permission(AccessControlEntry.Permission.OWNER) .resource("test-bis.") .grantedTo("test") - .build() - ) + .build()) .build(); + AccessControlEntry ace5 = AccessControlEntry.builder() .spec(AccessControlEntry.AccessControlEntrySpec.builder() .resourceType(AccessControlEntry.ResourceType.GROUP) @@ -193,9 +315,9 @@ void isNamespaceOwnerOfKafkaStream() { .permission(AccessControlEntry.Permission.OWNER) .resource("test-bis.") .grantedTo("test") - .build() - ) + .build()) .build(); + AccessControlEntry ace6 = AccessControlEntry.builder() .spec(AccessControlEntry.AccessControlEntrySpec.builder() .resourceType(AccessControlEntry.ResourceType.GROUP) @@ -203,9 +325,9 @@ void isNamespaceOwnerOfKafkaStream() { .permission(AccessControlEntry.Permission.OWNER) .resource("test-ter.") .grantedTo("test") - .build() - ) + .build()) .build(); + AccessControlEntry ace7 = AccessControlEntry.builder() .spec(AccessControlEntry.AccessControlEntrySpec.builder() .resourceType(AccessControlEntry.ResourceType.TOPIC) @@ -213,22 +335,16 @@ void isNamespaceOwnerOfKafkaStream() { .permission(AccessControlEntry.Permission.OWNER) .resource("test-qua.") .grantedTo("test") - .build() - ) + .build()) .build(); when(aclService.findAllGrantedToNamespace(ns)) .thenReturn(List.of(ace1, ace2, ace3, ace4, ace5, ace6, ace7)); - assertTrue( - streamService.isNamespaceOwnerOfKafkaStream(ns, "test.stream")); - Assertions.assertFalse( - streamService.isNamespaceOwnerOfKafkaStream(ns, "test-bis.stream"), "ACL are LITERAL"); - Assertions.assertFalse( - streamService.isNamespaceOwnerOfKafkaStream(ns, "test-ter.stream"), "Topic ACL missing"); - Assertions.assertFalse( - streamService.isNamespaceOwnerOfKafkaStream(ns, "test-qua.stream"), "Group ACL missing"); - Assertions.assertFalse( - streamService.isNamespaceOwnerOfKafkaStream(ns, "test-nop.stream"), "No ACL"); + assertTrue(streamService.isNamespaceOwnerOfKafkaStream(ns, "test.stream")); + assertFalse(streamService.isNamespaceOwnerOfKafkaStream(ns, "test-bis.stream"), "ACL are LITERAL"); + assertFalse(streamService.isNamespaceOwnerOfKafkaStream(ns, "test-ter.stream"), "Topic ACL missing"); + assertFalse(streamService.isNamespaceOwnerOfKafkaStream(ns, "test-qua.stream"), "Group ACL missing"); + assertFalse(streamService.isNamespaceOwnerOfKafkaStream(ns, "test-nop.stream"), "No ACL"); } } diff --git a/src/test/java/com/michelin/ns4kafka/util/RegexUtilsTest.java b/src/test/java/com/michelin/ns4kafka/util/RegexUtilsTest.java new file mode 100644 index 00000000..8e6217e8 --- /dev/null +++ b/src/test/java/com/michelin/ns4kafka/util/RegexUtilsTest.java @@ -0,0 +1,159 @@ +package com.michelin.ns4kafka.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Regex utils test. + */ +class RegexUtilsTest { + @Test + void defaultStringToRegexPattern() { + assertEquals(List.of("^.*$"), RegexUtils.wildcardStringsToRegexPatterns(List.of(""))); + assertEquals(List.of("^.*$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("*"))); + } + + @Test + void simpleWildcardToRegexPattern() { + assertEquals(List.of("^prefix.*$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("prefix*"))); + assertEquals(List.of("^.*suffix$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("*suffix"))); + assertEquals(List.of("^abc\\..*$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("abc.*"))); + assertEquals(List.of("^item.$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("item?"))); + } + + @Test + void complexWildcardToRegexPattern() { + assertEquals(List.of("^prefix.*suffix$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("prefix*suffix"))); + assertEquals(List.of("^...xyz$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("???xyz"))); + assertEquals(List.of("^.*\\.topic.$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("*.topic?"))); + assertEquals(List.of("^.*\\.topic.$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("*.topic?"))); + assertEquals(List.of("^abc.\\..*-test.$"), RegexUtils.wildcardStringsToRegexPatterns(List.of("abc?.*-test?"))); + } + + @Test + void multipleWildcardsToRegexPatterns() { + assertEquals(List.of("^prefix.*$", "^.*suffix$"), + RegexUtils.wildcardStringsToRegexPatterns(List.of("prefix*", "*suffix"))); + } + + @Test + void noFilterRegexPattern() { + assertTrue(RegexUtils.filterByPattern("topic1", List.of("^.*$"))); + } + + @Test + void prefixFilterWithRegexPattern() { + List pattern = List.of("^prefix.*$"); + assertTrue(RegexUtils.filterByPattern("prefix.topic", pattern)); + assertTrue(RegexUtils.filterByPattern("prefix1.topic", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.topic", pattern)); + assertFalse(RegexUtils.filterByPattern("topic", pattern)); + } + + @Test + void suffixFilterWithRegexPattern() { + List pattern = List.of("^.*-dev$"); + assertTrue(RegexUtils.filterByPattern("abc.topic-dev", pattern)); + assertTrue(RegexUtils.filterByPattern("xyz.stream-dev", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.topic-dev2", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.topic-test", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.topic.dev", pattern)); + assertFalse(RegexUtils.filterByPattern("topic", pattern)); + } + + @Test + void complexFilterWithRegexPattern() { + List pattern = List.of("^abc.\\..*-test.$"); + assertTrue(RegexUtils.filterByPattern("abc1.topic-test2", pattern)); + assertTrue(RegexUtils.filterByPattern("abc1.stream-test2", pattern)); + assertFalse(RegexUtils.filterByPattern("abc1.topic-test20", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.topic-test2", pattern)); + assertFalse(RegexUtils.filterByPattern("abc1.topic-test", pattern)); + assertFalse(RegexUtils.filterByPattern("abc1.topic-prod2", pattern)); + } + + @Test + void filterWithMultipleRegexPattern() { + List pattern = List.of("^prefix1.*$", "^prefix2.*$"); + assertTrue(RegexUtils.filterByPattern("prefix1.topic", pattern)); + assertTrue(RegexUtils.filterByPattern("prefix2.topic", pattern)); + assertFalse(RegexUtils.filterByPattern("prefix3.topic", pattern)); + assertFalse(RegexUtils.filterByPattern("topic", pattern)); + } + + /** + * Functional tests for wildcard filter. + */ + @Test + void noFilterWildcard() { + List pattern1 = RegexUtils.wildcardStringsToRegexPatterns(List.of("*")); + assertTrue(RegexUtils.filterByPattern("prefix.myTopic", pattern1)); + assertTrue(RegexUtils.filterByPattern("prefix10.yourSchema", pattern1)); + assertTrue(RegexUtils.filterByPattern("whatever.whatsoever", pattern1)); + assertTrue(RegexUtils.filterByPattern("whatever", pattern1)); + + List patterns2 = RegexUtils.wildcardStringsToRegexPatterns(List.of("")); + assertTrue(RegexUtils.filterByPattern("prefix.myTopic", patterns2)); + assertTrue(RegexUtils.filterByPattern("prefix10.yourSchema", patterns2)); + assertTrue(RegexUtils.filterByPattern("whatever.whatsoever", patterns2)); + assertTrue(RegexUtils.filterByPattern("whatever", patterns2)); + } + + @Test + void prefixFilterWithWildcard() { + List pattern = RegexUtils.wildcardStringsToRegexPatterns(List.of("abc.my*")); + assertTrue(RegexUtils.filterByPattern("abc.myTopic", pattern)); + assertTrue(RegexUtils.filterByPattern("abc.myStream", pattern)); + assertTrue(RegexUtils.filterByPattern("abc.myConnect.xyz", pattern)); + assertTrue(RegexUtils.filterByPattern("abc.my", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.topic", pattern)); + assertFalse(RegexUtils.filterByPattern("myTopic", pattern)); + } + + @Test + void suffixFilterWithWildcard() { + List pattern = RegexUtils.wildcardStringsToRegexPatterns(List.of("*-test")); + assertTrue(RegexUtils.filterByPattern("abc.myTopic-test", pattern)); + assertTrue(RegexUtils.filterByPattern("xyz.myStream-test", pattern)); + assertTrue(RegexUtils.filterByPattern("-test", pattern)); + assertTrue(RegexUtils.filterByPattern("myTopic-test", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.topic", pattern)); + assertFalse(RegexUtils.filterByPattern("myTopic-dev", pattern)); + assertFalse(RegexUtils.filterByPattern("myTopic-test1", pattern)); + assertFalse(RegexUtils.filterByPattern("myTopic-test-dev", pattern)); + } + + @Test + void filterWithMultipleWildcard() { + List pattern = RegexUtils.wildcardStringsToRegexPatterns(List.of("abc.myT*op?c")); + assertTrue(RegexUtils.filterByPattern("abc.myTopic", pattern)); + assertTrue(RegexUtils.filterByPattern("abc.myTopicTopic", pattern)); + assertTrue(RegexUtils.filterByPattern("abc.myTaaaaaopac", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.myTopiiic", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.yourTopic", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.myTopic.suffix", pattern)); + } + + @Test + void prefixAndSuffixFilterWithWildcard() { + List pattern = RegexUtils.wildcardStringsToRegexPatterns(List.of("*.myTopic?")); + assertTrue(RegexUtils.filterByPattern("abc.myTopic1", pattern)); + assertTrue(RegexUtils.filterByPattern("prefix.myTopic2", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.myTopic", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.myTopic.suffix", pattern)); + assertFalse(RegexUtils.filterByPattern("abc.myTopic13", pattern)); + } + + @Test + void filterWithMultipleSameWildcard() { + List pattern = RegexUtils.wildcardStringsToRegexPatterns(List.of("***.myTopic")); + assertTrue(RegexUtils.filterByPattern("abc.myTopic", pattern)); + assertTrue(RegexUtils.filterByPattern("prefix.myTopic", pattern)); + assertTrue(RegexUtils.filterByPattern(".myTopic", pattern)); + assertFalse(RegexUtils.filterByPattern("prefix.myStream", pattern)); + } +}