Repository: kroxylicious/kroxylicious
Pull request #205 (2023-03-20 04:07:05)

Bump log4j-bom from 2.17.2 to 2.20.0
Bumps log4j-bom from 2.17.2 to 2.20.0.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.logging.log4j:log4j-bom&package-manager=maven&previous-version=2.17.2&new-version=2.20.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/kroxylicious-multitenant/src/main/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilter.java b/kroxylicious-multitenant/src/main/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilter.java
index 0f2bea00..af33cf04 100644
--- a/kroxylicious-multitenant/src/main/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilter.java
+++ b/kroxylicious-multitenant/src/main/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilter.java
@@ -12,10 +12,18 @@ import org.apache.kafka.common.message.CreateTopicsRequestData;
 import org.apache.kafka.common.message.CreateTopicsResponseData;
 import org.apache.kafka.common.message.DeleteTopicsRequestData;
 import org.apache.kafka.common.message.DeleteTopicsResponseData;
+import org.apache.kafka.common.message.DescribeGroupsRequestData;
+import org.apache.kafka.common.message.DescribeGroupsResponseData;
 import org.apache.kafka.common.message.FetchRequestData;
 import org.apache.kafka.common.message.FetchResponseData;
 import org.apache.kafka.common.message.FindCoordinatorRequestData;
 import org.apache.kafka.common.message.FindCoordinatorResponseData;
+import org.apache.kafka.common.message.HeartbeatRequestData;
+import org.apache.kafka.common.message.HeartbeatResponseData;
+import org.apache.kafka.common.message.JoinGroupRequestData;
+import org.apache.kafka.common.message.JoinGroupResponseData;
+import org.apache.kafka.common.message.LeaveGroupRequestData;
+import org.apache.kafka.common.message.LeaveGroupResponseData;
 import org.apache.kafka.common.message.ListGroupsRequestData;
 import org.apache.kafka.common.message.ListGroupsResponseData;
 import org.apache.kafka.common.message.ListOffsetsRequestData;
@@ -32,6 +40,8 @@ import org.apache.kafka.common.message.ProduceRequestData;
 import org.apache.kafka.common.message.ProduceResponseData;
 import org.apache.kafka.common.message.RequestHeaderData;
 import org.apache.kafka.common.message.ResponseHeaderData;
+import org.apache.kafka.common.message.SyncGroupRequestData;
+import org.apache.kafka.common.message.SyncGroupResponseData;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,11 +49,19 @@ import io.kroxylicious.proxy.filter.CreateTopicsRequestFilter;
 import io.kroxylicious.proxy.filter.CreateTopicsResponseFilter;
 import io.kroxylicious.proxy.filter.DeleteTopicsRequestFilter;
 import io.kroxylicious.proxy.filter.DeleteTopicsResponseFilter;
+import io.kroxylicious.proxy.filter.DescribeGroupsRequestFilter;
+import io.kroxylicious.proxy.filter.DescribeGroupsResponseFilter;
 import io.kroxylicious.proxy.filter.FetchRequestFilter;
 import io.kroxylicious.proxy.filter.FetchResponseFilter;
 import io.kroxylicious.proxy.filter.FindCoordinatorRequestFilter;
 import io.kroxylicious.proxy.filter.FindCoordinatorResponseFilter;
+import io.kroxylicious.proxy.filter.HeartbeatRequestFilter;
+import io.kroxylicious.proxy.filter.HeartbeatResponseFilter;
+import io.kroxylicious.proxy.filter.JoinGroupRequestFilter;
+import io.kroxylicious.proxy.filter.JoinGroupResponseFilter;
 import io.kroxylicious.proxy.filter.KrpcFilterContext;
+import io.kroxylicious.proxy.filter.LeaveGroupRequestFilter;
+import io.kroxylicious.proxy.filter.LeaveGroupResponseFilter;
 import io.kroxylicious.proxy.filter.ListGroupsRequestFilter;
 import io.kroxylicious.proxy.filter.ListGroupsResponseFilter;
 import io.kroxylicious.proxy.filter.ListOffsetsRequestFilter;
@@ -58,6 +76,8 @@ import io.kroxylicious.proxy.filter.OffsetForLeaderEpochRequestFilter;
 import io.kroxylicious.proxy.filter.OffsetForLeaderEpochResponseFilter;
 import io.kroxylicious.proxy.filter.ProduceRequestFilter;
 import io.kroxylicious.proxy.filter.ProduceResponseFilter;
+import io.kroxylicious.proxy.filter.SyncGroupRequestFilter;
+import io.kroxylicious.proxy.filter.SyncGroupResponseFilter;
 
 /**
  * Simple multi-tenant filter.
@@ -80,7 +100,12 @@ public class MultiTenantTransformationFilter
         OffsetCommitRequestFilter, OffsetCommitResponseFilter,
         OffsetForLeaderEpochRequestFilter, OffsetForLeaderEpochResponseFilter,
         FindCoordinatorRequestFilter, FindCoordinatorResponseFilter,
-        ListGroupsRequestFilter, ListGroupsResponseFilter {
+        ListGroupsResponseFilter,
+        JoinGroupRequestFilter,
+        SyncGroupRequestFilter,
+        LeaveGroupRequestFilter,
+        HeartbeatRequestFilter,
+        DescribeGroupsRequestFilter, DescribeGroupsResponseFilter {
     private static final Logger LOGGER = LoggerFactory.getLogger(MultiTenantTransformationFilter.class);
 
     @Override
@@ -221,11 +246,6 @@ public class MultiTenantTransformationFilter
         context.forwardResponse(response);
     }
 
-    @Override
-    public void onListGroupsRequest(RequestHeaderData header, ListGroupsRequestData request, KrpcFilterContext context) {
-        context.forwardRequest(request);
-    }
-
     @Override
     public void onListGroupsResponse(ResponseHeaderData header, ListGroupsResponseData response, KrpcFilterContext context) {
         var tenantPrefix = getTenantPrefix(context);
@@ -235,6 +255,46 @@ public class MultiTenantTransformationFilter
         context.forwardResponse(response);
     }
 
+    @Override
+    public void onJoinGroupRequest(RequestHeaderData header, JoinGroupRequestData request, KrpcFilterContext context) {
+        var tenantPrefix = getTenantPrefix(context);
+        request.setGroupId(tenantPrefix + request.groupId());
+        context.forwardRequest(request);
+    }
+
+    @Override
+    public void onSyncGroupRequest(RequestHeaderData header, SyncGroupRequestData request, KrpcFilterContext context) {
+        var tenantPrefix = getTenantPrefix(context);
+        request.setGroupId(tenantPrefix + request.groupId());
+        context.forwardRequest(request);
+    }
+
+    @Override
+    public void onLeaveGroupRequest(RequestHeaderData header, LeaveGroupRequestData request, KrpcFilterContext context) {
+        var tenantPrefix = getTenantPrefix(context);
+        request.setGroupId(tenantPrefix + request.groupId());
+        context.forwardRequest(request);
+    }
+
+    @Override
+    public void onHeartbeatRequest(RequestHeaderData header, HeartbeatRequestData request, KrpcFilterContext context) {
+        var tenantPrefix = getTenantPrefix(context);
+        request.setGroupId(tenantPrefix + request.groupId());
+        context.forwardRequest(request);
+    }
+
+    @Override
+    public void onDescribeGroupsRequest(RequestHeaderData header, DescribeGroupsRequestData request, KrpcFilterContext context) {
+        request.setGroups(request.groups().stream().map(group -> applyTenantPrefix(context, group)).toList());
+        context.forwardRequest(request);
+    }
+
+    @Override
+    public void onDescribeGroupsResponse(ResponseHeaderData header, DescribeGroupsResponseData response, KrpcFilterContext context) {
+        response.groups().forEach(group -> removeTenantPrefix(context, group::groupId, group::setGroupId, false));
+        context.forwardResponse(response);
+    }
+
     private void applyTenantPrefix(KrpcFilterContext context, Supplier<String> getter, Consumer<String> setter, boolean ignoreEmpty) {
         String clientSideName = getter.get();
         if (ignoreEmpty && (clientSideName == null || clientSideName.isEmpty())) {
diff --git a/pom.xml b/pom.xml
index 1b07fab8..24635998 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,7 @@
         <junit.version>5.9.2</junit.version>
         <kafka.version>3.4.0</kafka.version>
         <kroxy.extension.version>0.1</kroxy.extension.version>
-        <log4j.version>2.17.2</log4j.version>
+        <log4j.version>2.20.0</log4j.version>
         <netty.version>4.1.90.Final</netty.version>
         <netty.io_uring.version>0.0.14.Final</netty.io_uring.version>
         <netty.epoll.classifier>linux-x86_64</netty.epoll.classifier>
@@ -444,7 +444,7 @@
             <plugin>
                 <groupId>org.asciidoctor</groupId>
                 <artifactId>asciidoctor-maven-plugin</artifactId>
-                <version>2.2.2</version>
+                <version>2.2.3</version>
                 <executions>
                     <execution>
                         <id>convert-to-html</id>


Test patch:
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java b/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java
index 256976de..936f6b51 100644
--- a/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java
@@ -22,6 +22,7 @@ import java.util.stream.Stream;
 
 import org.apache.kafka.clients.CommonClientConfigs;
 import org.apache.kafka.clients.admin.Admin;
+import org.apache.kafka.clients.admin.ConsumerGroupDescription;
 import org.apache.kafka.clients.admin.ConsumerGroupListing;
 import org.apache.kafka.clients.admin.CreateTopicsResult;
 import org.apache.kafka.clients.admin.DeleteTopicsResult;
@@ -38,6 +39,7 @@ import org.apache.kafka.clients.producer.KafkaProducer;
 import org.apache.kafka.clients.producer.ProducerConfig;
 import org.apache.kafka.clients.producer.ProducerRecord;
 import org.apache.kafka.clients.producer.RecordMetadata;
+import org.apache.kafka.common.ConsumerGroupState;
 import org.apache.kafka.common.TopicCollection;
 import org.apache.kafka.common.TopicCollection.TopicNameCollection;
 import org.apache.kafka.common.TopicPartition;
@@ -52,6 +54,8 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,7 +66,9 @@ import io.kroxylicious.testing.kafka.junit5ext.KafkaClusterExtension;
 import static io.kroxylicious.proxy.Utils.startProxy;
 import static org.assertj.core.api.Assertions.allOf;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 @ExtendWith(KafkaClusterExtension.class)
 public class MultiTenantIT {
@@ -190,30 +196,73 @@ public class MultiTenantIT {
         }
     }
 
-    @Test
-    public void tenantConsumeWithGroup(KafkaCluster cluster) throws Exception {
+    private enum ConsumerStyle {
+        ASSIGN,
+        SUBSCRIBE
+    }
+
+    @ParameterizedTest
+    @EnumSource
+    public void tenantConsumeWithGroup(ConsumerStyle consumerStyle, KafkaCluster cluster) throws Exception {
+        String config = getConfig(PROXY_ADDRESS, cluster);
+        try (var proxy = startProxy(config)) {
+            createTopics(TENANT1_PROXY_ADDRESS, List.of(NEW_TOPIC_1));
+            runConsumerInOrderToCreateGroup(TENANT1_PROXY_ADDRESS, "Tenant1Group", NEW_TOPIC_1, consumerStyle);
+            verifyConsumerGroupsWithList(TENANT1_PROXY_ADDRESS, Set.of("Tenant1Group"));
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource
+    public void tenantGroupIsolation(ConsumerStyle consumerStyle, KafkaCluster cluster) throws Exception {
         String config = getConfig(PROXY_ADDRESS, cluster);
         try (var proxy = startProxy(config)) {
             createTopics(TENANT1_PROXY_ADDRESS, List.of(NEW_TOPIC_1));
-            createConsumerWithGroup(TENANT1_PROXY_ADDRESS, "Tenant1Group", NEW_TOPIC_1);
-            verifyConsumerGroups(TENANT1_PROXY_ADDRESS, "Tenant1Group");
+            runConsumerInOrderToCreateGroup(TENANT1_PROXY_ADDRESS, "Tenant1Group", NEW_TOPIC_1, consumerStyle);
+
+            createTopics(TENANT2_PROXY_ADDRESS, List.of(NEW_TOPIC_1));
+            runConsumerInOrderToCreateGroup(TENANT2_PROXY_ADDRESS, "Tenant2Group", NEW_TOPIC_1, consumerStyle);
+            verifyConsumerGroupsWithList(TENANT2_PROXY_ADDRESS, Set.of("Tenant2Group"));
         }
     }
 
     @Test
-    public void tenantGroupIsolation(KafkaCluster cluster) throws Exception {
+    public void describeGroup(KafkaCluster cluster) throws Exception {
         String config = getConfig(PROXY_ADDRESS, cluster);
         try (var proxy = startProxy(config)) {
             createTopics(TENANT1_PROXY_ADDRESS, List.of(NEW_TOPIC_1));
-            createConsumerWithGroup(TENANT1_PROXY_ADDRESS, "Tenant1Group", NEW_TOPIC_1);
+            runConsumerInOrderToCreateGroup(TENANT1_PROXY_ADDRESS, "Tenant1Group", NEW_TOPIC_1, ConsumerStyle.ASSIGN);
 
             createTopics(TENANT2_PROXY_ADDRESS, List.of(NEW_TOPIC_1));
-            createConsumerWithGroup(TENANT2_PROXY_ADDRESS, "Tenant2Group", NEW_TOPIC_1);
-            verifyConsumerGroups(TENANT2_PROXY_ADDRESS, "Tenant2Group");
+            runConsumerInOrderToCreateGroup(TENANT2_PROXY_ADDRESS, "Tenant2Group", NEW_TOPIC_1, ConsumerStyle.ASSIGN);
+
+            verifyConsumerGroupsWithDescribe(TENANT1_PROXY_ADDRESS, Set.of("Tenant1Group"), Set.of("Tenant2Group", "idontexist"));
+            verifyConsumerGroupsWithDescribe(TENANT2_PROXY_ADDRESS, Set.of("Tenant2Group"), Set.of("Tenant1Group", "idontexist"));
         }
     }
 
-    private void createConsumerWithGroup(String proxyAddress, String groupId, NewTopic topic) {
+    private void verifyConsumerGroupsWithDescribe(String proxyAddress, Set<String> expectedPresent, Set<String> expectedAbsent) throws Exception {
+        try (var admin = Admin.create(commonConfig(proxyAddress, Map.of()))) {
+            var describedGroups = admin.describeConsumerGroups(Stream.concat(expectedPresent.stream(), expectedAbsent.stream()).toList()).all().get();
+            assertThat(describedGroups).hasSize(expectedAbsent.size() + expectedPresent.size());
+
+            var actualPresent = retainKeySubset(describedGroups, expectedPresent);
+            var actualAbsent = retainKeySubset(describedGroups, expectedAbsent);
+
+            // The consumer group comes back as "dead" when it doesn't exist, so we have to check that it's not dead/it is dead if we don't expect to see it
+            assertThat(actualPresent).allSatisfy((s, consumerGroupDescription) -> assertThat(consumerGroupDescription.state()).isNotIn(ConsumerGroupState.DEAD));
+            assertThat(actualAbsent).allSatisfy((s, consumerGroupDescription) -> assertThat(consumerGroupDescription.state()).isIn(ConsumerGroupState.DEAD));
+        }
+    }
+
+    @NotNull
+    private static Map<String, ConsumerGroupDescription> retainKeySubset(Map<String, ConsumerGroupDescription> groups, Set<String> retainedKeys) {
+        var copy = new HashMap<>(groups);
+        copy.keySet().retainAll(retainedKeys);
+        return copy;
+    }
+
+    private void runConsumerInOrderToCreateGroup(String proxyAddress, String groupId, NewTopic topic, ConsumerStyle consumerStyle) {
         try (var consumer = new KafkaConsumer<String, String>(commonConfig(proxyAddress, Map.of(
                 ConsumerConfig.GROUP_ID_CONFIG, groupId,
                 ConsumerConfig.ALLOW_AUTO_CREATE_TOPICS_CONFIG, Boolean.FALSE.toString(),
@@ -222,17 +271,22 @@ public class MultiTenantIT {
                 ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class,
                 ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class)))) {
 
-            var topicPartitions = List.of(new TopicPartition(topic.name(), 0));
-            consumer.assign(topicPartitions);
+            if (consumerStyle == ConsumerStyle.ASSIGN) {
+                var topicPartitions = List.of(new TopicPartition(topic.name(), 0));
+                consumer.assign(topicPartitions);
+            }
+            else {
+                consumer.subscribe(List.of(topic.name()));
+            }
 
             consumer.poll(Duration.ofSeconds(1));
         }
     }
 
-    private void verifyConsumerGroups(String proxyAddress, String... expectedGroupIds) throws InterruptedException, ExecutionException {
+    private void verifyConsumerGroupsWithList(String proxyAddress, Set<String> expectedGroupIds) throws Exception {
         try (var admin = Admin.create(commonConfig(proxyAddress, Map.of()))) {
             var groups = admin.listConsumerGroups().all().get().stream().map(ConsumerGroupListing::groupId).toList();
-            assertThat(groups).containsExactlyInAnyOrderElementsOf(Set.of(expectedGroupIds));
+            assertThat(groups).containsExactlyInAnyOrderElementsOf(expectedGroupIds);
         }
     }
 
diff --git a/kroxylicious-multitenant/src/test/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilterTest.java b/kroxylicious-multitenant/src/test/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilterTest.java
index 13c2597a..83fa1054 100644
--- a/kroxylicious-multitenant/src/test/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilterTest.java
+++ b/kroxylicious-multitenant/src/test/java/io/kroxylicious/proxy/filter/multitenant/MultiTenantTransformationFilterTest.java
@@ -131,7 +131,7 @@ class MultiTenantTransformationFilterTest {
     }
 
     public static Stream<Arguments> requests() throws Exception {
-        return requestResponseTestDefinitions().map(t -> Arguments.of(t.testName(), t.apiKey(), t.header(), t.request()));
+        return requestResponseTestDefinitions().filter(td -> td.request() != null).map(td -> Arguments.of(td.testName(), td.apiKey(), td.header(), td.request()));
     }
 
     @ParameterizedTest(name = "{0}")
@@ -150,7 +150,7 @@ class MultiTenantTransformationFilterTest {
     }
 
     public static Stream<Arguments> responses() throws Exception {
-        return requestResponseTestDefinitions().map(t -> Arguments.of(t.testName(), t.apiKey(), t.header(), t.response()));
+        return requestResponseTestDefinitions().filter(td -> td.response() != null).map(td -> Arguments.of(td.testName(), td.apiKey(), td.header(), td.response()));
     }
 
     @ParameterizedTest(name = "{0}")
diff --git a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/DescribeGroups.test.yaml b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/DescribeGroups.test.yaml
new file mode 100644
index 00000000..997c1922
--- /dev/null
+++ b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/DescribeGroups.test.yaml
@@ -0,0 +1,32 @@
+#
+# Copyright Kroxylicious Authors.
+#
+# Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+#
+
+- apiMessageType: DESCRIBE_GROUPS
+  version: 5
+  request:
+    payload:
+      groups: [mygroup]
+      includeAuthorizedOperations:  false
+    diff:
+      - op: replace
+        path: "/groups/0"
+        value: tenant1-mygroup
+  response:
+    payload:
+      throttleTimeMs: 0
+      groups:
+        - groupId: tenant1-mygroup
+          errorCode: 0
+          groupState: stable
+          protocolType: consumer
+          protocolData: protocoldata
+          members: []
+          authorizedOperations: 0
+    diff:
+      - op: replace
+        path: "/groups/0/groupId"
+        value: mygroup
+  disabled: false
diff --git a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/FindCoordinator.test.yaml b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/FindCoordinator.test.yaml
index f31c8ee9..287258ef 100644
--- a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/FindCoordinator.test.yaml
+++ b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/FindCoordinator.test.yaml
@@ -10,18 +10,18 @@
     payload:
       keyType: 0
       coordinatorKeys:
-        - test-group
+        - mygroup
       timeoutMs: 0
       validateOnly: false
     diff:
       - op: replace
         path: /coordinatorKeys/0
-        value: tenant1-test-group
+        value: tenant1-mygroup
   response:
     payload:
       throttleTimeMs: 0
       coordinators:
-        - key: tenant1-test-group
+        - key: tenant1-mygroup
           nodeId: 0
           host: localhost
           port: 12345
@@ -30,5 +30,5 @@
     diff:
       - op: replace
         path: "/coordinators/0/key"
-        value: test-group
+        value: mygroup
   disabled: false
diff --git a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/Heartbeat.test.yaml b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/Heartbeat.test.yaml
new file mode 100644
index 00000000..f502f3ce
--- /dev/null
+++ b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/Heartbeat.test.yaml
@@ -0,0 +1,19 @@
+#
+# Copyright Kroxylicious Authors.
+#
+# Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+#
+
+- apiMessageType: HEARTBEAT
+  version: 4
+  request:
+    payload:
+      groupId: mygroup
+      generationId: 0
+      memberId: mymemberid
+      groupInstanceId: mygroupinstanceid
+    diff:
+      - op: replace
+        path: "/groupId"
+        value: tenant1-mygroup
+  disabled: false
diff --git a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/JoinGroup.test.yaml b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/JoinGroup.test.yaml
new file mode 100644
index 00000000..0982fe3c
--- /dev/null
+++ b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/JoinGroup.test.yaml
@@ -0,0 +1,23 @@
+#
+# Copyright Kroxylicious Authors.
+#
+# Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+#
+
+- apiMessageType: JOIN_GROUP
+  version: 9
+  request:
+    payload:
+      groupId: mygroup
+      sessionTimeoutMs: 0
+      rebalanceTimeoutMs: 0
+      memberId: mymemberid
+      groupInstanceId: mygroupinstanceid
+      protocolType: consumer
+      protocols: []
+      reason: reason
+    diff:
+      - op: replace
+        path: /groupId
+        value: tenant1-mygroup
+  disabled: false
diff --git a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/LeaveGroup.test.yaml b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/LeaveGroup.test.yaml
new file mode 100644
index 00000000..c88ee040
--- /dev/null
+++ b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/LeaveGroup.test.yaml
@@ -0,0 +1,17 @@
+#
+# Copyright Kroxylicious Authors.
+#
+# Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+#
+
+- apiMessageType: LEAVE_GROUP
+  version: 5
+  request:
+    payload:
+      groupId: mygroup
+      members: []
+    diff:
+      - op: replace
+        path: "/groupId"
+        value: tenant1-mygroup
+  disabled: false
diff --git a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/ListGroups.test.yaml b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/ListGroups.test.yaml
index 8a61579a..658e6b03 100644
--- a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/ListGroups.test.yaml
+++ b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/ListGroups.test.yaml
@@ -7,10 +7,6 @@
 - apiMessageType: LIST_GROUPS
   description: Group mapping
   version: 4
-  request:
-    payload:
-      statesFilter: []
-    diff: []
   response:
     payload:
       throttleTimeMs: 0
@@ -28,10 +24,6 @@
 - apiMessageType: LIST_GROUPS
   description: Group filtering
   version: 4
-  request:
-    payload:
-      statesFilter: [ ]
-    diff: [ ]
   response:
     payload:
       throttleTimeMs: 0
diff --git a/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/SyncGroup.test.yaml b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/SyncGroup.test.yaml
new file mode 100644
index 00000000..20ab043e
--- /dev/null
+++ b/kroxylicious-multitenant/src/test/resources/io/kroxylicious/proxy/filter/multitenant/SyncGroup.test.yaml
@@ -0,0 +1,22 @@
+#
+# Copyright Kroxylicious Authors.
+#
+# Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+#
+
+- apiMessageType: SYNC_GROUP
+  version: 5
+  request:
+    payload:
+      groupId: mygroup
+      generationId: 0
+      memberId: mymemberid
+      groupInstanceId: mygroupinstanceid
+      protocolType: consumer
+      protocolName: protocolname
+      assignments: []
+    diff:
+      - op: replace
+        path: "/groupId"
+        value: tenant1-mygroup
+  disabled: false