From 7baa0e32890a7308560356dacbc9bfb7124eef37 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Thu, 15 Jan 2026 14:43:25 +0100 Subject: [PATCH 1/4] [CHA-1427] support campaigns --- .../getstream/chat/java/models/Campaign.java | 497 ++++++++++++++++++ .../chat/java/services/CampaignService.java | 44 ++ .../io/getstream/chat/java/CampaignTest.java | 202 +++++++ 3 files changed, 743 insertions(+) create mode 100644 src/main/java/io/getstream/chat/java/models/Campaign.java create mode 100644 src/main/java/io/getstream/chat/java/services/CampaignService.java create mode 100644 src/test/java/io/getstream/chat/java/CampaignTest.java diff --git a/src/main/java/io/getstream/chat/java/models/Campaign.java b/src/main/java/io/getstream/chat/java/models/Campaign.java new file mode 100644 index 00000000..00c60d87 --- /dev/null +++ b/src/main/java/io/getstream/chat/java/models/Campaign.java @@ -0,0 +1,497 @@ +package io.getstream.chat.java.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.getstream.chat.java.models.Campaign.CampaignCreateRequestData.CampaignCreateRequest; +import io.getstream.chat.java.models.Campaign.CampaignQueryRequestData.CampaignQueryRequest; +import io.getstream.chat.java.models.Campaign.CampaignStartRequestData.CampaignStartRequest; +import io.getstream.chat.java.models.Campaign.CampaignUpdateRequestData.CampaignUpdateRequest; +import io.getstream.chat.java.models.framework.StreamRequest; +import io.getstream.chat.java.models.framework.StreamResponseObject; +import io.getstream.chat.java.services.CampaignService; +import io.getstream.chat.java.services.framework.Client; +import java.util.Date; +import java.util.List; +import java.util.Map; +import lombok.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; + +@Data +@NoArgsConstructor +public class Campaign { + @NotNull + @JsonProperty("id") + private String id; + + @Nullable + @JsonProperty("name") + private String name; + + @Nullable + @JsonProperty("description") + private String description; + + @NotNull + @JsonProperty("sender_id") + private String senderId; + + @Nullable + @JsonProperty("status") + private String status; + + @Nullable + @JsonProperty("segment_ids") + private List segmentIds; + + @Nullable + @JsonProperty("user_ids") + private List userIds; + + @Nullable + @JsonProperty("message_template") + private MessageTemplate messageTemplate; + + @Nullable + @JsonProperty("channel_template") + private ChannelTemplate channelTemplate; + + @Nullable + @JsonProperty("create_channels") + private Boolean createChannels; + + @Nullable + @JsonProperty("skip_push") + private Boolean skipPush; + + @Nullable + @JsonProperty("skip_webhook") + private Boolean skipWebhook; + + @Nullable + @JsonProperty("scheduled_for") + private Date scheduledFor; + + @Nullable + @JsonProperty("stop_at") + private Date stopAt; + + @NotNull + @JsonProperty("created_at") + private Date createdAt; + + @NotNull + @JsonProperty("updated_at") + private Date updatedAt; + + @Data + @NoArgsConstructor + public static class MessageTemplate { + @Nullable + @JsonProperty("text") + private String text; + + @Nullable + @JsonProperty("attachments") + private List> attachments; + + @Nullable + @JsonProperty("custom") + private Map custom; + } + + @Data + @NoArgsConstructor + public static class MemberTemplate { + @NotNull + @JsonProperty("user_id") + private String userId; + + @Nullable + @JsonProperty("channel_role") + private String channelRole; + + @Nullable + @JsonProperty("custom") + private Map custom; + } + + @Data + @NoArgsConstructor + public static class ChannelTemplate { + @NotNull + @JsonProperty("type") + private String type; + + @Nullable + @JsonProperty("id") + private String id; + + @Nullable + @JsonProperty("members") + private List members; + + @Nullable + @JsonProperty("members_template") + private List membersTemplate; + + @Nullable + @JsonProperty("custom") + private Map custom; + } + + @Builder( + builderClassName = "CampaignCreateRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + @Getter + @EqualsAndHashCode(callSuper = false) + public static class CampaignCreateRequestData { + @Nullable + @JsonProperty("id") + private String id; + + @Nullable + @JsonProperty("name") + private String name; + + @Nullable + @JsonProperty("description") + private String description; + + @Nullable + @JsonProperty("sender_id") + private String senderId; + + @Nullable + @JsonProperty("segment_ids") + private List segmentIds; + + @Nullable + @JsonProperty("user_ids") + private List userIds; + + @Nullable + @JsonProperty("message_template") + private MessageTemplate messageTemplate; + + @Nullable + @JsonProperty("channel_template") + private ChannelTemplate channelTemplate; + + @Nullable + @JsonProperty("create_channels") + private Boolean createChannels; + + @Nullable + @JsonProperty("skip_push") + private Boolean skipPush; + + @Nullable + @JsonProperty("skip_webhook") + private Boolean skipWebhook; + + public static class CampaignCreateRequest extends StreamRequest { + @Override + protected Call generateCall(Client client) { + return client.create(CampaignService.class).create(this.internalBuild()); + } + } + } + + @Getter + @EqualsAndHashCode(callSuper = false) + @RequiredArgsConstructor + public static class CampaignGetRequest extends StreamRequest { + @NotNull private String id; + + @Override + protected Call generateCall(Client client) { + return client.create(CampaignService.class).get(id); + } + } + + @Builder( + builderClassName = "CampaignUpdateRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + @Getter + @EqualsAndHashCode(callSuper = false) + public static class CampaignUpdateRequestData { + @Nullable + @JsonProperty("name") + private String name; + + @Nullable + @JsonProperty("description") + private String description; + + @Nullable + @JsonProperty("sender_id") + private String senderId; + + @Nullable + @JsonProperty("segment_ids") + private List segmentIds; + + @Nullable + @JsonProperty("user_ids") + private List userIds; + + @Nullable + @JsonProperty("message_template") + private MessageTemplate messageTemplate; + + @Nullable + @JsonProperty("channel_template") + private ChannelTemplate channelTemplate; + + @Nullable + @JsonProperty("create_channels") + private Boolean createChannels; + + @Nullable + @JsonProperty("skip_push") + private Boolean skipPush; + + @Nullable + @JsonProperty("skip_webhook") + private Boolean skipWebhook; + + public static class CampaignUpdateRequest extends StreamRequest { + @NotNull private String id; + + private CampaignUpdateRequest(@NotNull String id) { + this.id = id; + } + + @Override + protected Call generateCall(Client client) { + return client.create(CampaignService.class).update(id, this.internalBuild()); + } + } + } + + @Getter + @EqualsAndHashCode(callSuper = false) + @RequiredArgsConstructor + public static class CampaignDeleteRequest extends StreamRequest { + @NotNull private String id; + + @Override + protected Call generateCall(Client client) { + return client.create(CampaignService.class).delete(id); + } + } + + @Builder( + builderClassName = "CampaignStartRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + @Getter + @EqualsAndHashCode(callSuper = false) + public static class CampaignStartRequestData { + @Nullable + @JsonProperty("scheduled_for") + private Date scheduledFor; + + @Nullable + @JsonProperty("stop_at") + private Date stopAt; + + public static class CampaignStartRequest extends StreamRequest { + @NotNull private String id; + + private CampaignStartRequest(@NotNull String id) { + this.id = id; + } + + @Override + protected Call generateCall(Client client) { + return client.create(CampaignService.class).start(id, this.internalBuild()); + } + } + } + + @Getter + @EqualsAndHashCode(callSuper = false) + @RequiredArgsConstructor + public static class CampaignStopRequest extends StreamRequest { + @NotNull private String id; + + @Override + protected Call generateCall(Client client) { + return client.create(CampaignService.class).stop(id); + } + } + + @Builder( + builderClassName = "CampaignQueryRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + @Getter + @EqualsAndHashCode(callSuper = false) + public static class CampaignQueryRequestData { + @Nullable + @JsonProperty("filter") + private Map filter; + + @Singular("sort") + @Nullable + @JsonProperty("sort") + private List sorts; + + @Nullable + @JsonProperty("limit") + private Integer limit; + + @Nullable + @JsonProperty("offset") + private Integer offset; + + public static class CampaignQueryRequest extends StreamRequest { + @Override + protected Call generateCall(Client client) { + return client.create(CampaignService.class).query(this.internalBuild()); + } + } + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class CampaignCreateResponse extends StreamResponseObject { + @NotNull + @JsonProperty("campaign") + private Campaign campaign; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class CampaignGetResponse extends StreamResponseObject { + @NotNull + @JsonProperty("campaign") + private Campaign campaign; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class CampaignUpdateResponse extends StreamResponseObject { + @NotNull + @JsonProperty("campaign") + private Campaign campaign; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class CampaignDeleteResponse extends StreamResponseObject { + @NotNull + @JsonProperty("campaign") + private Campaign campaign; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class CampaignStartResponse extends StreamResponseObject { + @NotNull + @JsonProperty("campaign") + private Campaign campaign; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class CampaignStopResponse extends StreamResponseObject { + @NotNull + @JsonProperty("campaign") + private Campaign campaign; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class CampaignQueryResponse extends StreamResponseObject { + @NotNull + @JsonProperty("campaigns") + private List campaigns; + } + + /** + * Creates a create request + * + * @return the created request + */ + @NotNull + public static CampaignCreateRequest create() { + return new CampaignCreateRequest(); + } + + /** + * Creates a get request + * + * @param id the campaign id + * @return the created request + */ + @NotNull + public static CampaignGetRequest get(@NotNull String id) { + return new CampaignGetRequest(id); + } + + /** + * Creates an update request + * + * @param id the campaign id + * @return the created request + */ + @NotNull + public static CampaignUpdateRequest update(@NotNull String id) { + return new CampaignUpdateRequest(id); + } + + /** + * Creates a delete request + * + * @param id the campaign id + * @return the created request + */ + @NotNull + public static CampaignDeleteRequest delete(@NotNull String id) { + return new CampaignDeleteRequest(id); + } + + /** + * Creates a start request + * + * @param id the campaign id + * @return the created request + */ + @NotNull + public static CampaignStartRequest start(@NotNull String id) { + return new CampaignStartRequest(id); + } + + /** + * Creates a stop request + * + * @param id the campaign id + * @return the created request + */ + @NotNull + public static CampaignStopRequest stop(@NotNull String id) { + return new CampaignStopRequest(id); + } + + /** + * Creates a query request + * + * @return the created request + */ + @NotNull + public static CampaignQueryRequest query() { + return new CampaignQueryRequest(); + } +} diff --git a/src/main/java/io/getstream/chat/java/services/CampaignService.java b/src/main/java/io/getstream/chat/java/services/CampaignService.java new file mode 100644 index 00000000..24ebd613 --- /dev/null +++ b/src/main/java/io/getstream/chat/java/services/CampaignService.java @@ -0,0 +1,44 @@ +package io.getstream.chat.java.services; + +import io.getstream.chat.java.models.Campaign.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; +import retrofit2.http.*; + +public interface CampaignService { + + @POST("campaigns") + @NotNull + Call create( + @NotNull @Body CampaignCreateRequestData campaignCreateRequestData); + + @GET("campaigns/{id}") + @NotNull + Call get(@NotNull @Path("id") String id); + + @PUT("campaigns/{id}") + @NotNull + Call update( + @NotNull @Path("id") String id, + @NotNull @Body CampaignUpdateRequestData campaignUpdateRequestData); + + @DELETE("campaigns/{id}") + @NotNull + Call delete(@NotNull @Path("id") String id); + + @POST("campaigns/{id}/start") + @NotNull + Call start( + @NotNull @Path("id") String id, + @Nullable @Body CampaignStartRequestData campaignStartRequestData); + + @POST("campaigns/{id}/stop") + @NotNull + Call stop(@NotNull @Path("id") String id); + + @POST("campaigns/query") + @NotNull + Call query( + @NotNull @Body CampaignQueryRequestData campaignQueryRequestData); +} diff --git a/src/test/java/io/getstream/chat/java/CampaignTest.java b/src/test/java/io/getstream/chat/java/CampaignTest.java new file mode 100644 index 00000000..e5b855e1 --- /dev/null +++ b/src/test/java/io/getstream/chat/java/CampaignTest.java @@ -0,0 +1,202 @@ +package io.getstream.chat.java; + +import io.getstream.chat.java.models.Campaign; +import io.getstream.chat.java.models.Campaign.MessageTemplate; +import io.getstream.chat.java.models.FilterCondition; +import io.getstream.chat.java.models.Sort; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class CampaignTest extends BasicTest { + + @DisplayName("Can create campaign") + @Test + void whenCreatingCampaign_thenCorrectName() { + String campaignName = "test campaign"; + MessageTemplate messageTemplate = new MessageTemplate(); + messageTemplate.setText("Hello"); + + Campaign campaign = + Assertions.assertDoesNotThrow( + () -> + Campaign.create() + .senderId(testUserRequestObject.getId()) + .userIds(List.of(testUsersRequestObjects.get(1).getId())) + .messageTemplate(messageTemplate) + .name(campaignName) + .request()) + .getCampaign(); + Assertions.assertEquals(campaignName, campaign.getName()); + Assertions.assertNotNull(campaign.getId()); + + // Cleanup + Assertions.assertDoesNotThrow(() -> Campaign.delete(campaign.getId()).request()); + } + + @DisplayName("Can perform campaign CRUD operations") + @Test + void whenPerformingCampaignCRUD_thenCorrectOperations() { + MessageTemplate messageTemplate = new MessageTemplate(); + messageTemplate.setText("Hello"); + + // Create + String originalName = "original name"; + Campaign created = + Assertions.assertDoesNotThrow( + () -> + Campaign.create() + .senderId(testUserRequestObject.getId()) + .userIds(List.of(testUsersRequestObjects.get(1).getId())) + .messageTemplate(messageTemplate) + .name(originalName) + .request()) + .getCampaign(); + Assertions.assertEquals(originalName, created.getName()); + String campaignId = created.getId(); + + pause(); + + // Read + Campaign retrieved = + Assertions.assertDoesNotThrow(() -> Campaign.get(campaignId).request()).getCampaign(); + Assertions.assertEquals(campaignId, retrieved.getId()); + Assertions.assertEquals(originalName, retrieved.getName()); + + // Update + String updatedName = "updated name"; + Campaign updated = + Assertions.assertDoesNotThrow( + () -> + Campaign.update(campaignId) + .name(updatedName) + .messageTemplate(messageTemplate) + .senderId(testUserRequestObject.getId()) + .userIds(List.of(testUsersRequestObjects.get(1).getId())) + .request()) + .getCampaign(); + Assertions.assertEquals(updatedName, updated.getName()); + + pause(); + + // Delete + Assertions.assertDoesNotThrow(() -> Campaign.delete(campaignId).request()); + } + + @DisplayName("Can start and stop campaign") + @Test + void whenStartingAndStoppingCampaign_thenCorrectOperations() { + MessageTemplate messageTemplate = new MessageTemplate(); + messageTemplate.setText("Hello"); + + Campaign created = + Assertions.assertDoesNotThrow( + () -> + Campaign.create() + .senderId(testUserRequestObject.getId()) + .userIds(List.of(testUsersRequestObjects.get(1).getId())) + .messageTemplate(messageTemplate) + .name("test campaign") + .request()) + .getCampaign(); + String campaignId = created.getId(); + + pause(); + + // Start with scheduling + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.HOUR, 1); + Date scheduledFor = cal.getTime(); + cal.add(Calendar.HOUR, 1); + Date stopAt = cal.getTime(); + + Campaign started = + Assertions.assertDoesNotThrow( + () -> + Campaign.start(campaignId) + .scheduledFor(scheduledFor) + .stopAt(stopAt) + .request()) + .getCampaign(); + Assertions.assertNotNull(started.getScheduledFor()); + + pause(); + + // Stop + Campaign stopped = + Assertions.assertDoesNotThrow(() -> Campaign.stop(campaignId).request()).getCampaign(); + Assertions.assertNotNull(stopped); + + // Cleanup + Assertions.assertDoesNotThrow(() -> Campaign.delete(campaignId).request()); + } + + @DisplayName("Can query campaigns") + @Test + void whenQueryingCampaigns_thenCorrectResults() { + MessageTemplate messageTemplate = new MessageTemplate(); + messageTemplate.setText("Hello"); + + Campaign created = + Assertions.assertDoesNotThrow( + () -> + Campaign.create() + .senderId(testUserRequestObject.getId()) + .userIds(List.of(testUsersRequestObjects.get(1).getId())) + .messageTemplate(messageTemplate) + .name("query test campaign") + .request()) + .getCampaign(); + String campaignId = created.getId(); + + pause(); + + // Query by ID + List campaigns = + Assertions.assertDoesNotThrow( + () -> + Campaign.query() + .filter(FilterCondition.eq("id", campaignId)) + .sorts( + List.of( + Sort.builder() + .field("created_at") + .direction(Sort.Direction.DESC) + .build())) + .limit(10) + .request()) + .getCampaigns(); + Assertions.assertTrue( + campaigns.stream().anyMatch(c -> c.getId().equals(campaignId)), + "Created campaign should be found in query results"); + + // Cleanup + Assertions.assertDoesNotThrow(() -> Campaign.delete(campaignId).request()); + } + + @DisplayName("Can create campaign without ID") + @Test + void whenCreatingCampaignWithoutId_thenIdIsGenerated() { + MessageTemplate messageTemplate = new MessageTemplate(); + messageTemplate.setText("Hello"); + + Campaign campaign = + Assertions.assertDoesNotThrow( + () -> + Campaign.create() + .senderId(testUserRequestObject.getId()) + .userIds(List.of(testUsersRequestObjects.get(1).getId())) + .messageTemplate(messageTemplate) + .name("auto id campaign") + .request()) + .getCampaign(); + Assertions.assertNotNull(campaign.getId()); + Assertions.assertFalse(campaign.getId().isEmpty()); + + // Cleanup + Assertions.assertDoesNotThrow(() -> Campaign.delete(campaign.getId()).request()); + } +} From ca29a254f4768ad6dd30ff9a9eae44d0720aaa4d Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Fri, 16 Jan 2026 10:28:00 +0100 Subject: [PATCH 2/4] fix format --- src/test/java/io/getstream/chat/java/CampaignTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/CampaignTest.java b/src/test/java/io/getstream/chat/java/CampaignTest.java index e5b855e1..5f455e24 100644 --- a/src/test/java/io/getstream/chat/java/CampaignTest.java +++ b/src/test/java/io/getstream/chat/java/CampaignTest.java @@ -116,10 +116,7 @@ void whenStartingAndStoppingCampaign_thenCorrectOperations() { Campaign started = Assertions.assertDoesNotThrow( () -> - Campaign.start(campaignId) - .scheduledFor(scheduledFor) - .stopAt(stopAt) - .request()) + Campaign.start(campaignId).scheduledFor(scheduledFor).stopAt(stopAt).request()) .getCampaign(); Assertions.assertNotNull(started.getScheduledFor()); From 39d2597260fd5e88b55b24590158b90a540f1b76 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Fri, 16 Jan 2026 10:42:17 +0100 Subject: [PATCH 3/4] fix tests --- .../chat/java/SharedLocationTest.java | 91 ++++++++++++++----- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/SharedLocationTest.java b/src/test/java/io/getstream/chat/java/SharedLocationTest.java index ee21a713..eeed63b6 100644 --- a/src/test/java/io/getstream/chat/java/SharedLocationTest.java +++ b/src/test/java/io/getstream/chat/java/SharedLocationTest.java @@ -36,6 +36,25 @@ static void setupSharedLocations() { .request()); } + /** + * Helper method to create an endAt date that is at least 2 minutes in the future + * to ensure it passes the server validation requirement of being more than 1 minute in the future. + */ + private Date createFutureEndAt() { + // Add 5 minutes to current time to ensure it's well beyond the 1 minute requirement + // This accounts for any network delays or processing time + return new Date(System.currentTimeMillis() + (5 * 60 * 1000)); + } + + /** + * Helper method to format a Date to ISO 8601 string format + */ + private String formatDateToISO(Date date) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat.format(date); + } + @DisplayName("Can send message with shared location and verify") @Test void whenSendingMessageWithSharedLocation_thenCanGetThroughUsersLocations() @@ -43,12 +62,18 @@ void whenSendingMessageWithSharedLocation_thenCanGetThroughUsersLocations() // Create a unique device ID for this test String deviceId = "device-" + UUID.randomUUID().toString(); + // Create a future endAt date (at least 2 minutes in the future) + Date endAtDate = createFutureEndAt(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + String endAtString = formatDateToISO(endAtDate); + // Create shared location request SharedLocationRequest locationRequest = new SharedLocation.SharedLocationRequest(); locationRequest.setCreatedByDeviceId(deviceId); locationRequest.setLatitude(40.7128); locationRequest.setLongitude(-74.0060); - locationRequest.setEndAt("2025-12-31T23:59:59Z"); + locationRequest.setEndAt(endAtString); locationRequest.setUserId(testUserRequestObject.getId()); // Convert request to SharedLocation @@ -56,9 +81,7 @@ void whenSendingMessageWithSharedLocation_thenCanGetThroughUsersLocations() sharedLocation.setCreatedByDeviceId(locationRequest.getCreatedByDeviceId()); sharedLocation.setLatitude(locationRequest.getLatitude()); sharedLocation.setLongitude(locationRequest.getLongitude()); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - sharedLocation.setEndAt(dateFormat.parse(locationRequest.getEndAt())); + sharedLocation.setEndAt(endAtDate); // Send message with shared location MessageRequestObject messageRequest = @@ -82,9 +105,11 @@ void whenSendingMessageWithSharedLocation_thenCanGetThroughUsersLocations() Assertions.assertEquals(40.7128, message.getSharedLocation().getLatitude()); Assertions.assertEquals(-74.0060, message.getSharedLocation().getLongitude()); - // Parse and verify the endAt date - Date expectedEndAt = dateFormat.parse("2025-12-31T23:59:59Z"); - Assertions.assertEquals(expectedEndAt, message.getSharedLocation().getEndAt()); + // Verify the endAt date is set (allowing for small timing differences) + Assertions.assertNotNull(message.getSharedLocation().getEndAt()); + // The endAt should be close to our expected time (within a reasonable range) + long timeDiff = Math.abs(message.getSharedLocation().getEndAt().getTime() - endAtDate.getTime()); + Assertions.assertTrue(timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); } @DisplayName("Can create live location, update it and verify the update") @@ -93,12 +118,18 @@ void whenUpdatingLiveLocation_thenCanGetUpdatedLocation() throws StreamException // Create a unique device ID for this test String deviceId = "device-" + UUID.randomUUID().toString(); + // Create a future endAt date (at least 2 minutes in the future) + Date initialEndAtDate = createFutureEndAt(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + String initialEndAtString = formatDateToISO(initialEndAtDate); + // Create initial shared location request SharedLocationRequest initialLocationRequest = new SharedLocation.SharedLocationRequest(); initialLocationRequest.setCreatedByDeviceId(deviceId); initialLocationRequest.setLatitude(40.7128); initialLocationRequest.setLongitude(-74.0060); - initialLocationRequest.setEndAt("2025-12-31T23:59:59Z"); + initialLocationRequest.setEndAt(initialEndAtString); initialLocationRequest.setUserId(testUserRequestObject.getId()); // Convert request to SharedLocation @@ -106,9 +137,7 @@ void whenUpdatingLiveLocation_thenCanGetUpdatedLocation() throws StreamException initialSharedLocation.setCreatedByDeviceId(initialLocationRequest.getCreatedByDeviceId()); initialSharedLocation.setLatitude(initialLocationRequest.getLatitude()); initialSharedLocation.setLongitude(initialLocationRequest.getLongitude()); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - initialSharedLocation.setEndAt(dateFormat.parse(initialLocationRequest.getEndAt())); + initialSharedLocation.setEndAt(initialEndAtDate); // Send initial message with shared location MessageRequestObject initialMessageRequest = @@ -125,13 +154,16 @@ void whenUpdatingLiveLocation_thenCanGetUpdatedLocation() throws StreamException .request() .getMessage(); - // Create updated location request + // Create updated location request with a new future endAt date + Date updatedEndAtDate = createFutureEndAt(); + String updatedEndAtString = formatDateToISO(updatedEndAtDate); + SharedLocationRequest updatedLocationRequest = new SharedLocation.SharedLocationRequest(); updatedLocationRequest.setMessageId(initialMessage.getId()); updatedLocationRequest.setCreatedByDeviceId(deviceId); updatedLocationRequest.setLatitude(40.7589); // Updated latitude updatedLocationRequest.setLongitude(-73.9851); // Updated longitude - updatedLocationRequest.setEndAt("2025-12-31T23:59:59Z"); + updatedLocationRequest.setEndAt(updatedEndAtString); updatedLocationRequest.setUserId(testUserRequestObject.getId()); // Update the location @@ -162,9 +194,10 @@ void whenUpdatingLiveLocation_thenCanGetUpdatedLocation() throws StreamException Assertions.assertEquals(40.7589, updatedLocation.getLatitude()); Assertions.assertEquals(-73.9851, updatedLocation.getLongitude()); - // Verify the endAt date - Date expectedEndAt = dateFormat.parse("2025-12-31T23:59:59Z"); - Assertions.assertEquals(expectedEndAt, updatedLocation.getEndAt()); + // Verify the endAt date is set (allowing for small timing differences) + Assertions.assertNotNull(updatedLocation.getEndAt()); + long timeDiff = Math.abs(updatedLocation.getEndAt().getTime() - updatedEndAtDate.getTime()); + Assertions.assertTrue(timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); } @DisplayName("Can verify live location in channel") @@ -173,12 +206,18 @@ void whenQueryingChannel_thenShouldHaveLiveLocation() throws StreamException, Pa // Create a unique device ID for this test String deviceId = "device-" + UUID.randomUUID().toString(); + // Create a future endAt date (at least 2 minutes in the future) + Date endAtDate = createFutureEndAt(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + String endAtString = formatDateToISO(endAtDate); + // Create shared location request SharedLocationRequest locationRequest = new SharedLocation.SharedLocationRequest(); locationRequest.setCreatedByDeviceId(deviceId); locationRequest.setLatitude(40.7128); locationRequest.setLongitude(-74.0060); - locationRequest.setEndAt("2025-12-31T23:59:59Z"); + locationRequest.setEndAt(endAtString); locationRequest.setUserId(testUserRequestObject.getId()); // Convert request to SharedLocation @@ -186,9 +225,7 @@ void whenQueryingChannel_thenShouldHaveLiveLocation() throws StreamException, Pa sharedLocation.setCreatedByDeviceId(locationRequest.getCreatedByDeviceId()); sharedLocation.setLatitude(locationRequest.getLatitude()); sharedLocation.setLongitude(locationRequest.getLongitude()); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - sharedLocation.setEndAt(dateFormat.parse(locationRequest.getEndAt())); + sharedLocation.setEndAt(endAtDate); // Send message with shared location MessageRequestObject messageRequest = @@ -212,9 +249,10 @@ void whenQueryingChannel_thenShouldHaveLiveLocation() throws StreamException, Pa Assertions.assertEquals(40.7128, message.getSharedLocation().getLatitude()); Assertions.assertEquals(-74.0060, message.getSharedLocation().getLongitude()); - // Parse and verify the endAt date - Date expectedEndAt = dateFormat.parse("2025-12-31T23:59:59Z"); - Assertions.assertEquals(expectedEndAt, message.getSharedLocation().getEndAt()); + // Verify the endAt date is set (allowing for small timing differences) + Assertions.assertNotNull(message.getSharedLocation().getEndAt()); + long timeDiff = Math.abs(message.getSharedLocation().getEndAt().getTime() - endAtDate.getTime()); + Assertions.assertTrue(timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); // Query the channel to verify it has the live location ChannelGetResponse response = @@ -238,6 +276,9 @@ void whenQueryingChannel_thenShouldHaveLiveLocation() throws StreamException, Pa Assertions.assertEquals(deviceId, channelLocation.getCreatedByDeviceId()); Assertions.assertEquals(40.7128, channelLocation.getLatitude()); Assertions.assertEquals(-74.0060, channelLocation.getLongitude()); - Assertions.assertEquals(expectedEndAt, channelLocation.getEndAt()); + Assertions.assertNotNull(channelLocation.getEndAt()); + // Allow for small timing differences in the endAt comparison + long channelTimeDiff = Math.abs(channelLocation.getEndAt().getTime() - endAtDate.getTime()); + Assertions.assertTrue(channelTimeDiff < 60000, "EndAt time should be within 1 minute of expected time"); } -} +} \ No newline at end of file From ab1bbb45028d92c71cd01de2e46759a770de3a46 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Fri, 16 Jan 2026 10:44:28 +0100 Subject: [PATCH 4/4] fix tests --- .../chat/java/SharedLocationTest.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/SharedLocationTest.java b/src/test/java/io/getstream/chat/java/SharedLocationTest.java index eeed63b6..b25fbba8 100644 --- a/src/test/java/io/getstream/chat/java/SharedLocationTest.java +++ b/src/test/java/io/getstream/chat/java/SharedLocationTest.java @@ -37,8 +37,8 @@ static void setupSharedLocations() { } /** - * Helper method to create an endAt date that is at least 2 minutes in the future - * to ensure it passes the server validation requirement of being more than 1 minute in the future. + * Helper method to create an endAt date that is at least 2 minutes in the future to ensure it + * passes the server validation requirement of being more than 1 minute in the future. */ private Date createFutureEndAt() { // Add 5 minutes to current time to ensure it's well beyond the 1 minute requirement @@ -46,9 +46,7 @@ private Date createFutureEndAt() { return new Date(System.currentTimeMillis() + (5 * 60 * 1000)); } - /** - * Helper method to format a Date to ISO 8601 string format - */ + /** Helper method to format a Date to ISO 8601 string format */ private String formatDateToISO(Date date) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -108,8 +106,10 @@ void whenSendingMessageWithSharedLocation_thenCanGetThroughUsersLocations() // Verify the endAt date is set (allowing for small timing differences) Assertions.assertNotNull(message.getSharedLocation().getEndAt()); // The endAt should be close to our expected time (within a reasonable range) - long timeDiff = Math.abs(message.getSharedLocation().getEndAt().getTime() - endAtDate.getTime()); - Assertions.assertTrue(timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); + long timeDiff = + Math.abs(message.getSharedLocation().getEndAt().getTime() - endAtDate.getTime()); + Assertions.assertTrue( + timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); } @DisplayName("Can create live location, update it and verify the update") @@ -197,7 +197,8 @@ void whenUpdatingLiveLocation_thenCanGetUpdatedLocation() throws StreamException // Verify the endAt date is set (allowing for small timing differences) Assertions.assertNotNull(updatedLocation.getEndAt()); long timeDiff = Math.abs(updatedLocation.getEndAt().getTime() - updatedEndAtDate.getTime()); - Assertions.assertTrue(timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); + Assertions.assertTrue( + timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); } @DisplayName("Can verify live location in channel") @@ -251,8 +252,10 @@ void whenQueryingChannel_thenShouldHaveLiveLocation() throws StreamException, Pa // Verify the endAt date is set (allowing for small timing differences) Assertions.assertNotNull(message.getSharedLocation().getEndAt()); - long timeDiff = Math.abs(message.getSharedLocation().getEndAt().getTime() - endAtDate.getTime()); - Assertions.assertTrue(timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); + long timeDiff = + Math.abs(message.getSharedLocation().getEndAt().getTime() - endAtDate.getTime()); + Assertions.assertTrue( + timeDiff < 60000, "EndAt time should be within 1 minute of expected time"); // Query the channel to verify it has the live location ChannelGetResponse response = @@ -279,6 +282,7 @@ void whenQueryingChannel_thenShouldHaveLiveLocation() throws StreamException, Pa Assertions.assertNotNull(channelLocation.getEndAt()); // Allow for small timing differences in the endAt comparison long channelTimeDiff = Math.abs(channelLocation.getEndAt().getTime() - endAtDate.getTime()); - Assertions.assertTrue(channelTimeDiff < 60000, "EndAt time should be within 1 minute of expected time"); + Assertions.assertTrue( + channelTimeDiff < 60000, "EndAt time should be within 1 minute of expected time"); } -} \ No newline at end of file +}