From 4002adb948a219f53ea21bc8c3bb02d0c5d0c7e3 Mon Sep 17 00:00:00 2001 From: javierdfm Date: Fri, 9 Jan 2026 15:48:51 +0100 Subject: [PATCH 1/4] feat: add channel batch updates support --- .../getstream/chat/java/models/Channel.java | 174 ++++++++++ .../chat/java/models/ChannelBatchUpdater.java | 232 +++++++++++++ .../chat/java/services/ChannelService.java | 4 + .../chat/java/ChannelBatchUpdaterTest.java | 321 ++++++++++++++++++ 4 files changed, 731 insertions(+) create mode 100644 src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java create mode 100644 src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java diff --git a/src/main/java/io/getstream/chat/java/models/Channel.java b/src/main/java/io/getstream/chat/java/models/Channel.java index ffe6d9e72..2fbf94f2e 100644 --- a/src/main/java/io/getstream/chat/java/models/Channel.java +++ b/src/main/java/io/getstream/chat/java/models/Channel.java @@ -18,6 +18,7 @@ import io.getstream.chat.java.models.Channel.ChannelUnMuteRequestData.ChannelUnMuteRequest; import io.getstream.chat.java.models.Channel.ChannelUpdateRequestData.ChannelUpdateRequest; import io.getstream.chat.java.models.Channel.MarkDeliveredRequestData.MarkDeliveredRequest; +import io.getstream.chat.java.models.ChannelBatchUpdater; import io.getstream.chat.java.models.ChannelType.BlocklistBehavior; import io.getstream.chat.java.models.ChannelType.ChannelTypeWithCommands; import io.getstream.chat.java.models.Message.MessageRequestObject; @@ -1875,4 +1876,177 @@ public static ChannelMemberPartialUpdateRequest unarchive( public static MarkDeliveredRequest markDelivered() { return new MarkDeliveredRequest(); } + + /** + * Channel batch operation types + */ + public enum ChannelBatchOperation { + @JsonProperty("addMembers") + ADD_MEMBERS, + @JsonProperty("removeMembers") + REMOVE_MEMBERS, + @JsonProperty("inviteMembers") + INVITE_MEMBERS, + @JsonProperty("assignRoles") + ASSIGN_ROLES, + @JsonProperty("addModerators") + ADD_MODERATORS, + @JsonProperty("demoteModerators") + DEMOTE_MODERATORS, + @JsonProperty("hide") + HIDE, + @JsonProperty("show") + SHOW, + @JsonProperty("archive") + ARCHIVE, + @JsonProperty("unarchive") + UNARCHIVE, + @JsonProperty("updateData") + UPDATE_DATA, + @JsonProperty("addFilterTags") + ADD_FILTER_TAGS, + @JsonProperty("removeFilterTags") + REMOVE_FILTER_TAGS + } + + /** + * Represents a member in batch operations + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ChannelBatchMemberRequest { + @NotNull + @JsonProperty("user_id") + private String userId; + + @Nullable + @JsonProperty("channel_role") + private String channelRole; + } + + /** + * Represents data that can be updated on channels in batch + */ + @Data + @NoArgsConstructor + public static class ChannelDataUpdate { + @Nullable + @JsonProperty("frozen") + private Boolean frozen; + + @Nullable + @JsonProperty("disabled") + private Boolean disabled; + + @Nullable + @JsonProperty("custom") + private Map custom; + + @Nullable + @JsonProperty("team") + private String team; + + @Nullable + @JsonProperty("config_overrides") + private Map configOverrides; + + @Nullable + @JsonProperty("auto_translation_enabled") + private Boolean autoTranslationEnabled; + + @Nullable + @JsonProperty("auto_translation_language") + private String autoTranslationLanguage; + } + + /** + * Represents filters for batch channel updates + */ + @Data + @NoArgsConstructor + public static class ChannelsBatchFilters { + @Nullable + @JsonProperty("cids") + private Object cids; + + @Nullable + @JsonProperty("types") + private Object types; + + @Nullable + @JsonProperty("filter_tags") + private Object filterTags; + } + + /** + * Represents options for batch channel updates + */ + @Data + @NoArgsConstructor + public static class ChannelsBatchOptions { + @NotNull + @JsonProperty("operation") + private ChannelBatchOperation operation; + + @NotNull + @JsonProperty("filter") + private ChannelsBatchFilters filter; + + @Nullable + @JsonProperty("members") + private List members; + + @Nullable + @JsonProperty("data") + private ChannelDataUpdate data; + + @Nullable + @JsonProperty("filter_tags_update") + private List filterTagsUpdate; + } + + @Getter + @EqualsAndHashCode + @RequiredArgsConstructor + public static class ChannelsBatchUpdateRequest + extends StreamRequest { + @NotNull private ChannelsBatchOptions options; + + @Override + protected Call generateCall(Client client) + throws StreamException { + return client.create(ChannelService.class).updateBatch(this); + } + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ChannelsBatchUpdateResponse extends StreamResponseObject { + @NotNull + @JsonProperty("task_id") + private String taskId; + } + + /** + * Creates a batch update request + * + * @param options the batch update options + * @return the created request + */ + @NotNull + public static ChannelsBatchUpdateRequest updateBatch(@NotNull ChannelsBatchOptions options) { + return new ChannelsBatchUpdateRequest(options); + } + + /** + * Returns a ChannelBatchUpdater instance for batch channel operations. + * + * @return ChannelBatchUpdater instance + */ + @NotNull + public static ChannelBatchUpdater channelBatchUpdater() { + return new ChannelBatchUpdater(); + } } diff --git a/src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java b/src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java new file mode 100644 index 000000000..658effbf6 --- /dev/null +++ b/src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java @@ -0,0 +1,232 @@ +package io.getstream.chat.java.models; + +import io.getstream.chat.java.models.Channel.*; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** + * Provides convenience methods for batch channel operations. + */ +public class ChannelBatchUpdater { + + /** + * Adds members to channels matching the filter. + * + * @param filter the filter to match channels + * @param members list of members to add + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest addMembers( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.ADD_MEMBERS); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Removes members from channels matching the filter. + * + * @param filter the filter to match channels + * @param members list of members to remove + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest removeMembers( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.REMOVE_MEMBERS); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Invites members to channels matching the filter. + * + * @param filter the filter to match channels + * @param members list of members to invite + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest inviteMembers( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.INVITE_MEMBERS); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Adds moderators to channels matching the filter. + * + * @param filter the filter to match channels + * @param members list of members to add as moderators + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest addModerators( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.ADD_MODERATORS); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Removes moderator role from members in channels matching the filter. + * + * @param filter the filter to match channels + * @param members list of members to demote from moderators + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest demoteModerators( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.DEMOTE_MODERATORS); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Assigns roles to members in channels matching the filter. + * + * @param filter the filter to match channels + * @param members list of members with roles to assign + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest assignRoles( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.ASSIGN_ROLES); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Hides channels matching the filter for the specified members. + * + * @param filter the filter to match channels + * @param members list of members for whom to hide channels + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest hide( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.HIDE); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Shows channels matching the filter for the specified members. + * + * @param filter the filter to match channels + * @param members list of members for whom to show channels + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest show( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.SHOW); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Archives channels matching the filter for the specified members. + * + * @param filter the filter to match channels + * @param members list of members for whom to archive channels + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest archive( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.ARCHIVE); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Unarchives channels matching the filter for the specified members. + * + * @param filter the filter to match channels + * @param members list of members for whom to unarchive channels + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest unarchive( + @NotNull ChannelsBatchFilters filter, @NotNull List members) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.UNARCHIVE); + options.setFilter(filter); + options.setMembers(members); + return Channel.updateBatch(options); + } + + /** + * Updates data on channels matching the filter. + * + * @param filter the filter to match channels + * @param data channel data to update + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest updateData( + @NotNull ChannelsBatchFilters filter, @NotNull ChannelDataUpdate data) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.UPDATE_DATA); + options.setFilter(filter); + options.setData(data); + return Channel.updateBatch(options); + } + + /** + * Adds filter tags to channels matching the filter. + * + * @param filter the filter to match channels + * @param tags list of filter tags to add + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest addFilterTags( + @NotNull ChannelsBatchFilters filter, @NotNull List tags) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.ADD_FILTER_TAGS); + options.setFilter(filter); + options.setFilterTagsUpdate(tags); + return Channel.updateBatch(options); + } + + /** + * Removes filter tags from channels matching the filter. + * + * @param filter the filter to match channels + * @param tags list of filter tags to remove + * @return the batch update request + */ + @NotNull + public ChannelsBatchUpdateRequest removeFilterTags( + @NotNull ChannelsBatchFilters filter, @NotNull List tags) { + ChannelsBatchOptions options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.REMOVE_FILTER_TAGS); + options.setFilter(filter); + options.setFilterTagsUpdate(tags); + return Channel.updateBatch(options); + } +} diff --git a/src/main/java/io/getstream/chat/java/services/ChannelService.java b/src/main/java/io/getstream/chat/java/services/ChannelService.java index 55ca0ea75..bfb342d4a 100644 --- a/src/main/java/io/getstream/chat/java/services/ChannelService.java +++ b/src/main/java/io/getstream/chat/java/services/ChannelService.java @@ -108,4 +108,8 @@ Call updateMemberPartial( Call markDelivered( @NotNull @Body MarkDeliveredRequestData markDeliveredOptions, @Query("user_id") String userId); + + @PUT("channels/batch") + Call updateBatch( + @NotNull @Body Channel.ChannelsBatchUpdateRequest request); } diff --git a/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java b/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java new file mode 100644 index 000000000..9cd8efaeb --- /dev/null +++ b/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java @@ -0,0 +1,321 @@ +package io.getstream.chat.java; + +import io.getstream.chat.java.exceptions.StreamException; +import io.getstream.chat.java.models.Channel; +import io.getstream.chat.java.models.Channel.*; +import io.getstream.chat.java.models.ChannelBatchUpdater; +import io.getstream.chat.java.models.TaskStatus; +import io.getstream.chat.java.models.TaskStatus.TaskStatusGetResponse; +import io.getstream.chat.java.models.User; +import io.getstream.chat.java.models.User.UserRequestObject; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ChannelBatchUpdaterTest extends BasicTest { + + @Test + @DisplayName("Can update channels batch with valid options") + void whenUpdatingChannelsBatchWithValidOptions_thenNoException() { + Assertions.assertDoesNotThrow( + () -> { + var ch1 = Assertions.assertDoesNotThrow(BasicTest::createRandomChannel).getChannel(); + Assertions.assertNotNull(ch1); + var ch2 = Assertions.assertDoesNotThrow(BasicTest::createRandomChannel).getChannel(); + Assertions.assertNotNull(ch2); + + var userToAdd = + UserRequestObject.builder() + .id(RandomStringUtils.randomAlphabetic(10)) + .name("Test User") + .build(); + User.upsert().user(userToAdd).request(); + + var filter = new ChannelsBatchFilters(); + Map cidsFilter = new HashMap<>(); + cidsFilter.put("$in", List.of(ch1.getCId(), ch2.getCId())); + filter.setCids(cidsFilter); + + var options = new ChannelsBatchOptions(); + options.setOperation(ChannelBatchOperation.ADD_MEMBERS); + options.setFilter(filter); + options.setMembers( + List.of(new ChannelBatchMemberRequest(userToAdd.getId(), null))); + + var response = Channel.updateBatch(options).request(); + Assertions.assertNotNull(response.getTaskId()); + Assertions.assertFalse(response.getTaskId().isEmpty()); + }); + } + + @Test + @DisplayName("ChannelBatchUpdater can add members") + void whenAddingMembers_thenNoException() { + Assertions.assertDoesNotThrow( + () -> { + var ch1 = Assertions.assertDoesNotThrow(BasicTest::createRandomChannel).getChannel(); + Assertions.assertNotNull(ch1); + var ch2 = Assertions.assertDoesNotThrow(BasicTest::createRandomChannel).getChannel(); + Assertions.assertNotNull(ch2); + + var user1 = + UserRequestObject.builder() + .id(RandomStringUtils.randomAlphabetic(10)) + .name("Test User 1") + .build(); + var user2 = + UserRequestObject.builder() + .id(RandomStringUtils.randomAlphabetic(10)) + .name("Test User 2") + .build(); + User.upsert().user(user1).user(user2).request(); + var usersToAdd = List.of(user1, user2); + + var filter = new ChannelsBatchFilters(); + Map cidsFilter = new HashMap<>(); + cidsFilter.put("$in", List.of(ch1.getCId(), ch2.getCId())); + filter.setCids(cidsFilter); + + var updater = Channel.channelBatchUpdater(); + var members = + usersToAdd.stream() + .map(user -> new ChannelBatchMemberRequest(user.getId(), null)) + .collect(Collectors.toList()); + + var response = updater.addMembers(filter, members).request(); + Assertions.assertNotNull(response.getTaskId()); + var taskId = response.getTaskId(); + + waitFor( + () -> { + TaskStatusGetResponse taskStatusResponse = + Assertions.assertDoesNotThrow(() -> TaskStatus.get(taskId).request()); + return "completed".equals(taskStatusResponse.getStatus()); + }); + + // Verify members were added by querying channels + waitFor( + () -> { + var ch1State = + Assertions.assertDoesNotThrow( + () -> + Channel.getOrCreate(ch1.getType(), ch1.getId()) + .request() + .getChannel()); + var ch1MemberIds = + ch1State.getMembers().stream() + .map(ChannelMember::getUserId) + .collect(Collectors.toList()); + return ch1MemberIds.containsAll( + usersToAdd.stream().map(user -> user.getId()).collect(Collectors.toList())); + }); + }); + } + + @Test + @DisplayName("ChannelBatchUpdater can remove members") + void whenRemovingMembers_thenNoException() { + Assertions.assertDoesNotThrow( + () -> { + var user1 = + UserRequestObject.builder() + .id(RandomStringUtils.randomAlphabetic(10)) + .name("Test User 1") + .build(); + var user2 = + UserRequestObject.builder() + .id(RandomStringUtils.randomAlphabetic(10)) + .name("Test User 2") + .build(); + User.upsert().user(user1).user(user2).request(); + var membersId = List.of(user1.getId(), user2.getId()); + + var ch1 = + Channel.getOrCreate("messaging", RandomStringUtils.randomAlphabetic(12)) + .data( + ChannelRequestObject.builder() + .createdBy(testUserRequestObject) + .members( + membersId.stream() + .map( + id -> + ChannelMemberRequestObject.builder() + .user(UserRequestObject.builder().id(id).build()) + .build()) + .collect(Collectors.toList())) + .build()) + .request() + .getChannel(); + + var ch2 = + Channel.getOrCreate("messaging", RandomStringUtils.randomAlphabetic(12)) + .data( + ChannelRequestObject.builder() + .createdBy(testUserRequestObject) + .members( + membersId.stream() + .map( + id -> + ChannelMemberRequestObject.builder() + .user(UserRequestObject.builder().id(id).build()) + .build()) + .collect(Collectors.toList())) + .build()) + .request() + .getChannel(); + + // Verify members are present + var ch1State = Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel(); + var ch2State = Channel.getOrCreate(ch2.getType(), ch2.getId()).request().getChannel(); + Assertions.assertTrue(ch1State.getMembers().size() >= 2); + Assertions.assertTrue(ch2State.getMembers().size() >= 2); + + var ch1MemberIds = + ch1State.getMembers().stream() + .map(ChannelMember::getUserId) + .collect(Collectors.toList()); + var ch2MemberIds = + ch2State.getMembers().stream() + .map(ChannelMember::getUserId) + .collect(Collectors.toList()); + Assertions.assertTrue(ch1MemberIds.containsAll(membersId)); + Assertions.assertTrue(ch2MemberIds.containsAll(membersId)); + + // Remove a member + var updater = Channel.channelBatchUpdater(); + var memberToRemove = membersId.get(0); + + var filter = new ChannelsBatchFilters(); + Map cidsFilter = new HashMap<>(); + cidsFilter.put("$in", List.of(ch1.getCId(), ch2.getCId())); + filter.setCids(cidsFilter); + + var response = + updater + .removeMembers(filter, List.of(new ChannelBatchMemberRequest(memberToRemove, null))) + .request(); + Assertions.assertNotNull(response.getTaskId()); + var taskId = response.getTaskId(); + + waitFor( + () -> { + TaskStatusGetResponse taskStatusResponse = + Assertions.assertDoesNotThrow(() -> TaskStatus.get(taskId).request()); + return "completed".equals(taskStatusResponse.getStatus()); + }); + + // Verify member was removed + waitFor( + () -> { + var ch1StateAfter = + Assertions.assertDoesNotThrow( + () -> + Channel.getOrCreate(ch1.getType(), ch1.getId()) + .request() + .getChannel()); + var ch1MemberIdsAfter = + ch1StateAfter.getMembers().stream() + .map(ChannelMember::getUserId) + .collect(Collectors.toList()); + return !ch1MemberIdsAfter.contains(memberToRemove); + }); + }); + } + + @Test + @DisplayName("ChannelBatchUpdater can archive channels") + void whenArchivingChannels_thenNoException() { + Assertions.assertDoesNotThrow( + () -> { + var user1 = + UserRequestObject.builder() + .id(RandomStringUtils.randomAlphabetic(10)) + .name("Test User 1") + .build(); + var user2 = + UserRequestObject.builder() + .id(RandomStringUtils.randomAlphabetic(10)) + .name("Test User 2") + .build(); + User.upsert().user(user1).user(user2).request(); + var membersId = List.of(user1.getId(), user2.getId()); + + var ch1 = + Channel.getOrCreate("messaging", RandomStringUtils.randomAlphabetic(12)) + .data( + ChannelRequestObject.builder() + .createdBy(testUserRequestObject) + .members( + membersId.stream() + .map( + id -> + ChannelMemberRequestObject.builder() + .user(UserRequestObject.builder().id(id).build()) + .build()) + .collect(Collectors.toList())) + .build()) + .request() + .getChannel(); + + var ch2 = + Channel.getOrCreate("messaging", RandomStringUtils.randomAlphabetic(12)) + .data( + ChannelRequestObject.builder() + .createdBy(testUserRequestObject) + .members( + membersId.stream() + .map( + id -> + ChannelMemberRequestObject.builder() + .user(UserRequestObject.builder().id(id).build()) + .build()) + .collect(Collectors.toList())) + .build()) + .request() + .getChannel(); + + var updater = Channel.channelBatchUpdater(); + + var filter = new ChannelsBatchFilters(); + Map cidsFilter = new HashMap<>(); + cidsFilter.put("$in", List.of(ch1.getCId(), ch2.getCId())); + filter.setCids(cidsFilter); + + var response = + updater + .archive(filter, List.of(new ChannelBatchMemberRequest(membersId.get(0), null))) + .request(); + Assertions.assertNotNull(response.getTaskId()); + var taskId = response.getTaskId(); + + waitFor( + () -> { + TaskStatusGetResponse taskStatusResponse = + Assertions.assertDoesNotThrow(() -> TaskStatus.get(taskId).request()); + return "completed".equals(taskStatusResponse.getStatus()); + }); + + // Verify channel was archived + waitFor( + () -> { + var ch1State = + Assertions.assertDoesNotThrow( + () -> + Channel.getOrCreate(ch1.getType(), ch1.getId()) + .request() + .getChannel()); + var member = + ch1State.getMembers().stream() + .filter(m -> m.getUserId().equals(membersId.get(0))) + .findFirst() + .orElse(null); + return member != null && member.getArchivedAt() != null; + }); + }); + } +} From 756ce8406f7ece2334aafc1c79339475bbdd038f Mon Sep 17 00:00:00 2001 From: javierdfm Date: Fri, 9 Jan 2026 15:54:08 +0100 Subject: [PATCH 2/4] fix: make format --- .../getstream/chat/java/models/Channel.java | 24 +++++-------------- .../chat/java/models/ChannelBatchUpdater.java | 4 +--- .../chat/java/ChannelBatchUpdaterTest.java | 20 +++++----------- 3 files changed, 13 insertions(+), 35 deletions(-) diff --git a/src/main/java/io/getstream/chat/java/models/Channel.java b/src/main/java/io/getstream/chat/java/models/Channel.java index 2fbf94f2e..4a903f754 100644 --- a/src/main/java/io/getstream/chat/java/models/Channel.java +++ b/src/main/java/io/getstream/chat/java/models/Channel.java @@ -18,7 +18,6 @@ import io.getstream.chat.java.models.Channel.ChannelUnMuteRequestData.ChannelUnMuteRequest; import io.getstream.chat.java.models.Channel.ChannelUpdateRequestData.ChannelUpdateRequest; import io.getstream.chat.java.models.Channel.MarkDeliveredRequestData.MarkDeliveredRequest; -import io.getstream.chat.java.models.ChannelBatchUpdater; import io.getstream.chat.java.models.ChannelType.BlocklistBehavior; import io.getstream.chat.java.models.ChannelType.ChannelTypeWithCommands; import io.getstream.chat.java.models.Message.MessageRequestObject; @@ -1877,9 +1876,7 @@ public static MarkDeliveredRequest markDelivered() { return new MarkDeliveredRequest(); } - /** - * Channel batch operation types - */ + /** Channel batch operation types */ public enum ChannelBatchOperation { @JsonProperty("addMembers") ADD_MEMBERS, @@ -1909,9 +1906,7 @@ public enum ChannelBatchOperation { REMOVE_FILTER_TAGS } - /** - * Represents a member in batch operations - */ + /** Represents a member in batch operations */ @Data @NoArgsConstructor @AllArgsConstructor @@ -1925,9 +1920,7 @@ public static class ChannelBatchMemberRequest { private String channelRole; } - /** - * Represents data that can be updated on channels in batch - */ + /** Represents data that can be updated on channels in batch */ @Data @NoArgsConstructor public static class ChannelDataUpdate { @@ -1960,9 +1953,7 @@ public static class ChannelDataUpdate { private String autoTranslationLanguage; } - /** - * Represents filters for batch channel updates - */ + /** Represents filters for batch channel updates */ @Data @NoArgsConstructor public static class ChannelsBatchFilters { @@ -1979,9 +1970,7 @@ public static class ChannelsBatchFilters { private Object filterTags; } - /** - * Represents options for batch channel updates - */ + /** Represents options for batch channel updates */ @Data @NoArgsConstructor public static class ChannelsBatchOptions { @@ -2014,8 +2003,7 @@ public static class ChannelsBatchUpdateRequest @NotNull private ChannelsBatchOptions options; @Override - protected Call generateCall(Client client) - throws StreamException { + protected Call generateCall(Client client) throws StreamException { return client.create(ChannelService.class).updateBatch(this); } } diff --git a/src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java b/src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java index 658effbf6..19114d505 100644 --- a/src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java +++ b/src/main/java/io/getstream/chat/java/models/ChannelBatchUpdater.java @@ -4,9 +4,7 @@ import java.util.List; import org.jetbrains.annotations.NotNull; -/** - * Provides convenience methods for batch channel operations. - */ +/** Provides convenience methods for batch channel operations. */ public class ChannelBatchUpdater { /** diff --git a/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java b/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java index 9cd8efaeb..e9ccb3ed5 100644 --- a/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java +++ b/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java @@ -1,9 +1,7 @@ package io.getstream.chat.java; -import io.getstream.chat.java.exceptions.StreamException; import io.getstream.chat.java.models.Channel; import io.getstream.chat.java.models.Channel.*; -import io.getstream.chat.java.models.ChannelBatchUpdater; import io.getstream.chat.java.models.TaskStatus; import io.getstream.chat.java.models.TaskStatus.TaskStatusGetResponse; import io.getstream.chat.java.models.User; @@ -44,8 +42,7 @@ void whenUpdatingChannelsBatchWithValidOptions_thenNoException() { var options = new ChannelsBatchOptions(); options.setOperation(ChannelBatchOperation.ADD_MEMBERS); options.setFilter(filter); - options.setMembers( - List.of(new ChannelBatchMemberRequest(userToAdd.getId(), null))); + options.setMembers(List.of(new ChannelBatchMemberRequest(userToAdd.getId(), null))); var response = Channel.updateBatch(options).request(); Assertions.assertNotNull(response.getTaskId()); @@ -104,9 +101,7 @@ void whenAddingMembers_thenNoException() { var ch1State = Assertions.assertDoesNotThrow( () -> - Channel.getOrCreate(ch1.getType(), ch1.getId()) - .request() - .getChannel()); + Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel()); var ch1MemberIds = ch1State.getMembers().stream() .map(ChannelMember::getUserId) @@ -197,7 +192,8 @@ void whenRemovingMembers_thenNoException() { var response = updater - .removeMembers(filter, List.of(new ChannelBatchMemberRequest(memberToRemove, null))) + .removeMembers( + filter, List.of(new ChannelBatchMemberRequest(memberToRemove, null))) .request(); Assertions.assertNotNull(response.getTaskId()); var taskId = response.getTaskId(); @@ -215,9 +211,7 @@ void whenRemovingMembers_thenNoException() { var ch1StateAfter = Assertions.assertDoesNotThrow( () -> - Channel.getOrCreate(ch1.getType(), ch1.getId()) - .request() - .getChannel()); + Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel()); var ch1MemberIdsAfter = ch1StateAfter.getMembers().stream() .map(ChannelMember::getUserId) @@ -306,9 +300,7 @@ void whenArchivingChannels_thenNoException() { var ch1State = Assertions.assertDoesNotThrow( () -> - Channel.getOrCreate(ch1.getType(), ch1.getId()) - .request() - .getChannel()); + Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel()); var member = ch1State.getMembers().stream() .filter(m -> m.getUserId().equals(membersId.get(0))) From 43d93a04b765894d406850d5b1ad538abb18e6b1 Mon Sep 17 00:00:00 2001 From: javierdfm Date: Fri, 9 Jan 2026 16:15:30 +0100 Subject: [PATCH 3/4] fix: serialization options --- src/main/java/io/getstream/chat/java/models/Channel.java | 2 +- .../java/io/getstream/chat/java/services/ChannelService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/getstream/chat/java/models/Channel.java b/src/main/java/io/getstream/chat/java/models/Channel.java index 4a903f754..10f75f6db 100644 --- a/src/main/java/io/getstream/chat/java/models/Channel.java +++ b/src/main/java/io/getstream/chat/java/models/Channel.java @@ -2004,7 +2004,7 @@ public static class ChannelsBatchUpdateRequest @Override protected Call generateCall(Client client) throws StreamException { - return client.create(ChannelService.class).updateBatch(this); + return client.create(ChannelService.class).updateBatch(this.options); } } diff --git a/src/main/java/io/getstream/chat/java/services/ChannelService.java b/src/main/java/io/getstream/chat/java/services/ChannelService.java index bfb342d4a..82a2b8e2e 100644 --- a/src/main/java/io/getstream/chat/java/services/ChannelService.java +++ b/src/main/java/io/getstream/chat/java/services/ChannelService.java @@ -111,5 +111,5 @@ Call markDelivered( @PUT("channels/batch") Call updateBatch( - @NotNull @Body Channel.ChannelsBatchUpdateRequest request); + @NotNull @Body Channel.ChannelsBatchOptions options); } From 6e8095edf543e730dbf558682f2e4fb8cf9a1ac7 Mon Sep 17 00:00:00 2001 From: javierdfm Date: Fri, 9 Jan 2026 16:42:28 +0100 Subject: [PATCH 4/4] fix: fix tests --- .../chat/java/ChannelBatchUpdaterTest.java | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java b/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java index e9ccb3ed5..b109e7fe3 100644 --- a/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java +++ b/src/test/java/io/getstream/chat/java/ChannelBatchUpdaterTest.java @@ -95,19 +95,32 @@ void whenAddingMembers_thenNoException() { return "completed".equals(taskStatusResponse.getStatus()); }); - // Verify members were added by querying channels + // Verify members were added to both channels waitFor( () -> { var ch1State = Assertions.assertDoesNotThrow( () -> Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel()); + var ch2State = + Assertions.assertDoesNotThrow( + () -> + Channel.getOrCreate(ch2.getType(), ch2.getId()).request().getChannel()); + if (ch1State.getMembers() == null || ch2State.getMembers() == null) { + return false; + } var ch1MemberIds = ch1State.getMembers().stream() .map(ChannelMember::getUserId) .collect(Collectors.toList()); - return ch1MemberIds.containsAll( - usersToAdd.stream().map(user -> user.getId()).collect(Collectors.toList())); + var ch2MemberIds = + ch2State.getMembers().stream() + .map(ChannelMember::getUserId) + .collect(Collectors.toList()); + var userIdsToAdd = + usersToAdd.stream().map(user -> user.getId()).collect(Collectors.toList()); + return ch1MemberIds.containsAll(userIdsToAdd) + && ch2MemberIds.containsAll(userIdsToAdd); }); }); } @@ -167,6 +180,8 @@ void whenRemovingMembers_thenNoException() { // Verify members are present var ch1State = Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel(); var ch2State = Channel.getOrCreate(ch2.getType(), ch2.getId()).request().getChannel(); + Assertions.assertNotNull(ch1State.getMembers()); + Assertions.assertNotNull(ch2State.getMembers()); Assertions.assertTrue(ch1State.getMembers().size() >= 2); Assertions.assertTrue(ch2State.getMembers().size() >= 2); @@ -205,18 +220,30 @@ void whenRemovingMembers_thenNoException() { return "completed".equals(taskStatusResponse.getStatus()); }); - // Verify member was removed + // Verify member was removed from both channels waitFor( () -> { var ch1StateAfter = Assertions.assertDoesNotThrow( () -> Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel()); + var ch2StateAfter = + Assertions.assertDoesNotThrow( + () -> + Channel.getOrCreate(ch2.getType(), ch2.getId()).request().getChannel()); + if (ch1StateAfter.getMembers() == null || ch2StateAfter.getMembers() == null) { + return false; + } var ch1MemberIdsAfter = ch1StateAfter.getMembers().stream() .map(ChannelMember::getUserId) .collect(Collectors.toList()); - return !ch1MemberIdsAfter.contains(memberToRemove); + var ch2MemberIdsAfter = + ch2StateAfter.getMembers().stream() + .map(ChannelMember::getUserId) + .collect(Collectors.toList()); + return !ch1MemberIdsAfter.contains(memberToRemove) + && !ch2MemberIdsAfter.contains(memberToRemove); }); }); } @@ -294,19 +321,34 @@ void whenArchivingChannels_thenNoException() { return "completed".equals(taskStatusResponse.getStatus()); }); - // Verify channel was archived + // Verify channel was archived for the specified member in both channels waitFor( () -> { var ch1State = Assertions.assertDoesNotThrow( () -> Channel.getOrCreate(ch1.getType(), ch1.getId()).request().getChannel()); - var member = + var ch2State = + Assertions.assertDoesNotThrow( + () -> + Channel.getOrCreate(ch2.getType(), ch2.getId()).request().getChannel()); + if (ch1State.getMembers() == null || ch2State.getMembers() == null) { + return false; + } + var ch1Member = ch1State.getMembers().stream() .filter(m -> m.getUserId().equals(membersId.get(0))) .findFirst() .orElse(null); - return member != null && member.getArchivedAt() != null; + var ch2Member = + ch2State.getMembers().stream() + .filter(m -> m.getUserId().equals(membersId.get(0))) + .findFirst() + .orElse(null); + return ch1Member != null + && ch1Member.getArchivedAt() != null + && ch2Member != null + && ch2Member.getArchivedAt() != null; }); }); }