From f1255e75be4ff310c548f871e1200960b1492121 Mon Sep 17 00:00:00 2001 From: Ali Momen Sani Date: Thu, 7 Aug 2025 12:21:42 +0200 Subject: [PATCH] fix: don't reset webhook events when updating other fields --- .../io/getstream/chat/java/models/App.java | 21 ++++++- .../java/io/getstream/chat/java/AppTest.java | 55 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/getstream/chat/java/models/App.java b/src/main/java/io/getstream/chat/java/models/App.java index 512ba9f4c..5fda9f28c 100644 --- a/src/main/java/io/getstream/chat/java/models/App.java +++ b/src/main/java/io/getstream/chat/java/models/App.java @@ -794,6 +794,7 @@ public static class AppUpdateRequestData { @NotNull @JsonProperty("reminders_interval") + @JsonInclude(Include.NON_DEFAULT) private int remindersInterval; @Nullable @@ -883,6 +884,7 @@ public static class AppUpdateRequestData { @Nullable @JsonProperty("webhook_events") + @JsonInclude(Include.NON_NULL) private List webhookEvents; @Nullable @@ -890,10 +892,13 @@ public static class AppUpdateRequestData { @JsonInclude(Include.NON_NULL) private Boolean multiTenantEnabled; + static final Date defaultDate = new Date(Long.MIN_VALUE); + @Nullable @JsonProperty("revoke_tokens_issued_before") - // This field can be sent as null - private Date revokeTokensIssuedBefore; + @JsonInclude(value = Include.CUSTOM, valueFilter = NonDefaultDateFilter.class) + @Builder.Default + private Date revokeTokensIssuedBefore = defaultDate; @Nullable @JsonProperty("channel_hide_members_only") @@ -907,6 +912,7 @@ public static class AppUpdateRequestData { @Nullable @JsonProperty("grants") + @JsonInclude(Include.NON_NULL) private Map> grants; @Nullable @@ -920,6 +926,17 @@ protected Call generateCall(Client client) { return client.create(AppService.class).update(this.internalBuild()); } } + + // Filter that excludes default date and included null (used to reset) + static class NonDefaultDateFilter { + // Return true if filtering out (excluding), false to include + @Override + public boolean equals(Object o) { + // only ever called with String value + Date other = (Date) o; + return defaultDate.equals(other); + } + } } public static class AppGetRateLimitsRequest extends StreamRequest { diff --git a/src/test/java/io/getstream/chat/java/AppTest.java b/src/test/java/io/getstream/chat/java/AppTest.java index 5264678bb..436417a6d 100644 --- a/src/test/java/io/getstream/chat/java/AppTest.java +++ b/src/test/java/io/getstream/chat/java/AppTest.java @@ -1,5 +1,6 @@ package io.getstream.chat.java; +import com.fasterxml.jackson.databind.ObjectMapper; import io.getstream.chat.java.exceptions.StreamException; import io.getstream.chat.java.models.App; import io.getstream.chat.java.models.App.AppCheckSnsResponse; @@ -11,11 +12,13 @@ import io.getstream.chat.java.models.Message; import io.getstream.chat.java.models.Message.MessageRequestObject; import io.getstream.chat.java.services.framework.DefaultClient; +import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; +import java.util.HashMap; import java.util.Properties; import java.util.Random; import org.junit.jupiter.api.Assertions; @@ -250,4 +253,56 @@ void whenUpdatingAppSettingsWithSNSEventHook_thenNoException() throws StreamExce throw e; } } + + @DisplayName("App Settings update webhook events") + @Test + void whenUpdatingAppSettings_thenDoesntAlwaysChangeWebhookEvents() { + var messageNewList = Arrays.asList("message.new"); + Assertions.assertDoesNotThrow(() -> App.update().webhookEvents(messageNewList).request()); + + var appConfig = Assertions.assertDoesNotThrow(() -> App.get().request()).getApp(); + Assertions.assertEquals(messageNewList, appConfig.getWebhookEvents()); + + // Updating another field should not change (reset) webhook events + Assertions.assertDoesNotThrow(() -> App.update().remindersInterval(60).request()); + + appConfig = Assertions.assertDoesNotThrow(() -> App.get().request()).getApp(); + Assertions.assertEquals(messageNewList, appConfig.getWebhookEvents()); + + // Reset webhook events to defaults using an empty list + Assertions.assertDoesNotThrow(() -> App.update().webhookEvents(new ArrayList<>()).request()); + appConfig = Assertions.assertDoesNotThrow(() -> App.get().request()).getApp(); + Assertions.assertTrue(appConfig.getWebhookEvents().size() > 1); + } + + @DisplayName("AppConfig encoding should not include null fields") + @Test + void whenEncodingAppConfig_thenNoNullFields() { + var appConfig = App.update().internalBuild(); + final ObjectMapper mapper = new ObjectMapper(); + + String json = Assertions.assertDoesNotThrow(() -> mapper.writeValueAsString(appConfig)); + + // When we didn't set any fields, the JSON should be empty + Assertions.assertEquals("{}", json); + + // We set some fields where we mean to reset them + var appConfigNull = + App.update() + .webhookEvents(new ArrayList<>()) + .revokeTokensIssuedBefore(null) + .grants(new HashMap<>()) + .internalBuild(); + + json = Assertions.assertDoesNotThrow(() -> mapper.writeValueAsString(appConfigNull)); + + Assertions.assertTrue( + json.contains("\"webhook_events\":[]"), "JSON should contain null webhook_events field"); + + Assertions.assertTrue( + json.contains("\"revoke_tokens_issued_before\":null"), + "JSON should contain null revoke_tokens_issued_before field"); + + Assertions.assertTrue(json.contains("\"grants\":{}"), "JSON should contain null grants field"); + } }