From e7edea3eb75d547f7fdf67a1324eab6f2032b7f4 Mon Sep 17 00:00:00 2001 From: Daksh Date: Thu, 12 Feb 2026 10:43:10 +0100 Subject: [PATCH 01/10] add team usage stats api support --- .../chat/java/models/TeamUsageStats.java | 231 ++++++++++++++++++ .../chat/java/services/StatsService.java | 14 ++ .../chat/java/TeamUsageStatsTest.java | 157 ++++++++++++ 3 files changed, 402 insertions(+) create mode 100644 src/main/java/io/getstream/chat/java/models/TeamUsageStats.java create mode 100644 src/main/java/io/getstream/chat/java/services/StatsService.java create mode 100644 src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java diff --git a/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java b/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java new file mode 100644 index 00000000..e5a6955e --- /dev/null +++ b/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java @@ -0,0 +1,231 @@ +package io.getstream.chat.java.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsRequestData.QueryTeamUsageStatsRequest; +import io.getstream.chat.java.models.framework.StreamRequest; +import io.getstream.chat.java.models.framework.StreamResponseObject; +import io.getstream.chat.java.services.StatsService; +import io.getstream.chat.java.services.framework.Client; +import java.util.List; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; + +/** Team-level usage statistics for multi-tenant apps. */ +@Data +@NoArgsConstructor +public class TeamUsageStats { + + /** Team identifier (empty string for users not assigned to any team). */ + @NotNull + @JsonProperty("team") + private String team; + + // Daily activity metrics (total = SUM of daily values) + + /** Daily active users. */ + @NotNull + @JsonProperty("users_daily") + private MetricStats usersDaily; + + /** Daily messages sent. */ + @NotNull + @JsonProperty("messages_daily") + private MetricStats messagesDaily; + + /** Daily translations. */ + @NotNull + @JsonProperty("translations_daily") + private MetricStats translationsDaily; + + /** Daily image moderations. */ + @NotNull + @JsonProperty("image_moderations_daily") + private MetricStats imageModerationDaily; + + // Peak metrics (total = MAX of daily values) + + /** Peak concurrent users. */ + @NotNull + @JsonProperty("concurrent_users") + private MetricStats concurrentUsers; + + /** Peak concurrent connections. */ + @NotNull + @JsonProperty("concurrent_connections") + private MetricStats concurrentConnections; + + // Rolling/cumulative metrics (total = LATEST daily value) + + /** Total users. */ + @NotNull + @JsonProperty("users_total") + private MetricStats usersTotal; + + /** Users active in last 24 hours. */ + @NotNull + @JsonProperty("users_last_24_hours") + private MetricStats usersLast24Hours; + + /** MAU - users active in last 30 days. */ + @NotNull + @JsonProperty("users_last_30_days") + private MetricStats usersLast30Days; + + /** Users active this month. */ + @NotNull + @JsonProperty("users_month_to_date") + private MetricStats usersMonthToDate; + + /** Engaged MAU. */ + @NotNull + @JsonProperty("users_engaged_last_30_days") + private MetricStats usersEngagedLast30Days; + + /** Engaged users this month. */ + @NotNull + @JsonProperty("users_engaged_month_to_date") + private MetricStats usersEngagedMonthToDate; + + /** Total messages. */ + @NotNull + @JsonProperty("messages_total") + private MetricStats messagesTotal; + + /** Messages in last 24 hours. */ + @NotNull + @JsonProperty("messages_last_24_hours") + private MetricStats messagesLast24Hours; + + /** Messages in last 30 days. */ + @NotNull + @JsonProperty("messages_last_30_days") + private MetricStats messagesLast30Days; + + /** Messages this month. */ + @NotNull + @JsonProperty("messages_month_to_date") + private MetricStats messagesMonthToDate; + + /** Statistics for a single metric with optional daily breakdown. */ + @Data + @NoArgsConstructor + public static class MetricStats { + /** Per-day values (only present in daily mode). */ + @Nullable + @JsonProperty("daily") + private List daily; + + /** Aggregated total value. */ + @NotNull + @JsonProperty("total") + private Long total; + } + + /** Represents a metric value for a specific date. */ + @Data + @NoArgsConstructor + public static class DailyValue { + /** Date in YYYY-MM-DD format. */ + @NotNull + @JsonProperty("date") + private String date; + + /** Metric value for this date. */ + @NotNull + @JsonProperty("value") + private Long value; + } + + @Builder( + builderClassName = "QueryTeamUsageStatsRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + @Getter + @EqualsAndHashCode + public static class QueryTeamUsageStatsRequestData { + /** + * Month in YYYY-MM format (e.g., '2026-01'). Mutually exclusive with start_date/end_date. + * Returns aggregated monthly values. + */ + @Nullable + @JsonProperty("month") + private String month; + + /** + * Start date in YYYY-MM-DD format. Used with end_date for custom date range. Returns daily + * breakdown. + */ + @Nullable + @JsonProperty("start_date") + private String startDate; + + /** + * End date in YYYY-MM-DD format. Used with start_date for custom date range. Returns daily + * breakdown. + */ + @Nullable + @JsonProperty("end_date") + private String endDate; + + /** Maximum number of teams to return per page (default: 30, max: 30). */ + @Nullable + @JsonProperty("limit") + private Integer limit; + + /** Cursor for pagination to fetch next page of teams. */ + @Nullable + @JsonProperty("next") + private String next; + + public static class QueryTeamUsageStatsRequest + extends StreamRequest { + @Override + protected Call generateCall(Client client) { + return client.create(StatsService.class).queryTeamUsageStats(this.internalBuild()); + } + } + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class QueryTeamUsageStatsResponse extends StreamResponseObject { + /** Array of team usage statistics. */ + @NotNull + @JsonProperty("teams") + private List teams; + + /** Cursor for pagination to fetch next page. */ + @Nullable + @JsonProperty("next") + private String next; + } + + /** + * Queries team-level usage statistics from the warehouse database. + * + *

Returns all 16 metrics grouped by team with cursor-based pagination. + * + *

Date Range Options (mutually exclusive): + * + *

    + *
  • Use 'month' parameter (YYYY-MM format) for monthly aggregated values + *
  • Use 'startDate'/'endDate' parameters (YYYY-MM-DD format) for daily breakdown + *
  • If neither provided, defaults to current month (monthly mode) + *
+ * + *

This endpoint is server-side only. + * + * @return the created request + */ + @NotNull + public static QueryTeamUsageStatsRequest queryTeamUsageStats() { + return new QueryTeamUsageStatsRequest(); + } +} diff --git a/src/main/java/io/getstream/chat/java/services/StatsService.java b/src/main/java/io/getstream/chat/java/services/StatsService.java new file mode 100644 index 00000000..05772042 --- /dev/null +++ b/src/main/java/io/getstream/chat/java/services/StatsService.java @@ -0,0 +1,14 @@ +package io.getstream.chat.java.services; + +import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsRequestData; +import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface StatsService { + @POST("stats/team_usage") + Call queryTeamUsageStats( + @NotNull @Body QueryTeamUsageStatsRequestData queryTeamUsageStatsRequestData); +} diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java new file mode 100644 index 00000000..254c35a3 --- /dev/null +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java @@ -0,0 +1,157 @@ +package io.getstream.chat.java; + +import io.getstream.chat.java.models.TeamUsageStats; +import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class TeamUsageStatsTest { + + @DisplayName("Can query team usage stats with default options") + @Test + void whenQueryingTeamUsageStatsWithDefaultOptions_thenNoException() { + QueryTeamUsageStatsResponse response = + Assertions.assertDoesNotThrow(() -> TeamUsageStats.queryTeamUsageStats().request()); + + Assertions.assertNotNull(response); + Assertions.assertNotNull(response.getTeams()); + // Teams list might be empty if there's no usage data + } + + @DisplayName("Can query team usage stats with month parameter") + @Test + void whenQueryingTeamUsageStatsWithMonth_thenNoException() { + // Use current month + String currentMonth = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")); + + QueryTeamUsageStatsResponse response = + Assertions.assertDoesNotThrow( + () -> TeamUsageStats.queryTeamUsageStats().month(currentMonth).request()); + + Assertions.assertNotNull(response); + Assertions.assertNotNull(response.getTeams()); + } + + @DisplayName("Can query team usage stats with date range") + @Test + void whenQueryingTeamUsageStatsWithDateRange_thenNoException() { + // Use last 7 days + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusDays(7); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + QueryTeamUsageStatsResponse response = + Assertions.assertDoesNotThrow( + () -> + TeamUsageStats.queryTeamUsageStats() + .startDate(startDate.format(formatter)) + .endDate(endDate.format(formatter)) + .request()); + + Assertions.assertNotNull(response); + Assertions.assertNotNull(response.getTeams()); + + // If there are teams with data, verify the daily breakdown is present + if (!response.getTeams().isEmpty()) { + TeamUsageStats team = response.getTeams().get(0); + Assertions.assertNotNull(team.getTeam()); + Assertions.assertNotNull(team.getUsersDaily()); + Assertions.assertNotNull(team.getMessagesDaily()); + } + } + + @DisplayName("Can query team usage stats with pagination") + @Test + void whenQueryingTeamUsageStatsWithPagination_thenNoException() { + // First page with limit + QueryTeamUsageStatsResponse firstPage = + Assertions.assertDoesNotThrow( + () -> TeamUsageStats.queryTeamUsageStats().limit(10).request()); + + Assertions.assertNotNull(firstPage); + Assertions.assertNotNull(firstPage.getTeams()); + + // If there's a next cursor, fetch the next page + if (firstPage.getNext() != null && !firstPage.getNext().isEmpty()) { + QueryTeamUsageStatsResponse secondPage = + Assertions.assertDoesNotThrow( + () -> + TeamUsageStats.queryTeamUsageStats() + .limit(10) + .next(firstPage.getNext()) + .request()); + + Assertions.assertNotNull(secondPage); + Assertions.assertNotNull(secondPage.getTeams()); + } + } + + @DisplayName("Can query team usage stats for last year and verify response structure") + @Test + void whenQueryingTeamUsageStats_thenResponseStructureIsCorrect() { + // Query last year to maximize chance of getting data + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusYears(1); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + QueryTeamUsageStatsResponse response = + Assertions.assertDoesNotThrow( + () -> + TeamUsageStats.queryTeamUsageStats() + .startDate(startDate.format(formatter)) + .endDate(endDate.format(formatter)) + .request()); + + Assertions.assertNotNull(response); + Assertions.assertNotNull(response.getTeams()); + + // Verify response structure + List teams = response.getTeams(); + if (!teams.isEmpty()) { + TeamUsageStats team = teams.get(0); + + // Verify all 16 metrics are present + Assertions.assertNotNull(team.getTeam(), "Team identifier should not be null"); + + // Daily activity metrics + Assertions.assertNotNull(team.getUsersDaily(), "users_daily should not be null"); + Assertions.assertNotNull(team.getMessagesDaily(), "messages_daily should not be null"); + Assertions.assertNotNull( + team.getTranslationsDaily(), "translations_daily should not be null"); + Assertions.assertNotNull( + team.getImageModerationDaily(), "image_moderations_daily should not be null"); + + // Peak metrics + Assertions.assertNotNull(team.getConcurrentUsers(), "concurrent_users should not be null"); + Assertions.assertNotNull( + team.getConcurrentConnections(), "concurrent_connections should not be null"); + + // Rolling/cumulative metrics + Assertions.assertNotNull(team.getUsersTotal(), "users_total should not be null"); + Assertions.assertNotNull( + team.getUsersLast24Hours(), "users_last_24_hours should not be null"); + Assertions.assertNotNull(team.getUsersLast30Days(), "users_last_30_days should not be null"); + Assertions.assertNotNull( + team.getUsersMonthToDate(), "users_month_to_date should not be null"); + Assertions.assertNotNull( + team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should not be null"); + Assertions.assertNotNull( + team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should not be null"); + Assertions.assertNotNull(team.getMessagesTotal(), "messages_total should not be null"); + Assertions.assertNotNull( + team.getMessagesLast24Hours(), "messages_last_24_hours should not be null"); + Assertions.assertNotNull( + team.getMessagesLast30Days(), "messages_last_30_days should not be null"); + Assertions.assertNotNull( + team.getMessagesMonthToDate(), "messages_month_to_date should not be null"); + + // Verify MetricStats structure + Assertions.assertNotNull( + team.getUsersDaily().getTotal(), "MetricStats total should not be null"); + } + } +} From ea53a4238add425e875e1a9797dbb454d5003078 Mon Sep 17 00:00:00 2001 From: Daksh Date: Thu, 12 Feb 2026 11:13:24 +0100 Subject: [PATCH 02/10] fix: exclude null fields from team usage stats request JSON Add @JsonInclude(NON_NULL) to prevent Jackson from serializing null values in the request body, which was causing 404 errors from the API. Co-Authored-By: Claude Opus 4.5 --- src/main/java/io/getstream/chat/java/models/TeamUsageStats.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java b/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java index e5a6955e..53df0a9b 100644 --- a/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java +++ b/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java @@ -1,5 +1,6 @@ package io.getstream.chat.java.models; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsRequestData.QueryTeamUsageStatsRequest; import io.getstream.chat.java.models.framework.StreamRequest; @@ -148,6 +149,7 @@ public static class DailyValue { buildMethodName = "internalBuild") @Getter @EqualsAndHashCode + @JsonInclude(JsonInclude.Include.NON_NULL) public static class QueryTeamUsageStatsRequestData { /** * Month in YYYY-MM format (e.g., '2026-01'). Mutually exclusive with start_date/end_date. From 59051ffa3cc67a641b7ab4f6a07ff6fe9a5279cb Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 02:20:13 +0100 Subject: [PATCH 03/10] test: add strict integration tests for Team Usage Stats API Add TeamUsageStatsIntegrationTest with: - Strict data correctness tests for sdk-test-team-1/2/3 - Verifies exact values for all 16 metrics - Uses dedicated credentials (STREAM_USAGE_STATS_KEY/SECRET) - Queries fixed date range (2026-02-17 to 2026-02-18) - Catches API/SDK regressions by asserting exact values Also refactor TeamUsageStatsTest to require at least one team in response structure validation. Co-Authored-By: Claude Opus 4.5 --- .../java/TeamUsageStatsIntegrationTest.java | 440 ++++++++++++++++++ .../chat/java/TeamUsageStatsTest.java | 86 ++-- 2 files changed, 483 insertions(+), 43 deletions(-) create mode 100644 src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java new file mode 100644 index 00000000..000d7c3c --- /dev/null +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java @@ -0,0 +1,440 @@ +package io.getstream.chat.java; + +import static org.junit.jupiter.api.Assertions.*; + +import io.getstream.chat.java.exceptions.StreamException; +import io.getstream.chat.java.models.TeamUsageStats; +import io.getstream.chat.java.models.TeamUsageStats.MetricStats; +import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; +import io.getstream.chat.java.services.framework.DefaultClient; +import java.util.Properties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for Team Usage Stats API. Uses dedicated test credentials from + * STREAM_USAGE_STATS_KEY and STREAM_USAGE_STATS_SECRET environment variables. + * + *

These tests verify that the SDK correctly parses all response data from the backend. + */ +public class TeamUsageStatsIntegrationTest { + + @BeforeAll + static void setup() { + // Use dedicated credentials for usage stats testing + String apiKey = System.getenv("STREAM_USAGE_STATS_KEY"); + String apiSecret = System.getenv("STREAM_USAGE_STATS_SECRET"); + + // Fall back to standard credentials if usage stats specific ones aren't set + if (apiKey == null || apiKey.isEmpty()) { + apiKey = System.getenv("STREAM_KEY"); + } + if (apiSecret == null || apiSecret.isEmpty()) { + apiSecret = System.getenv("STREAM_SECRET"); + } + + if (apiKey == null || apiSecret == null) { + throw new IllegalStateException( + "Missing credentials. Set STREAM_USAGE_STATS_KEY/STREAM_USAGE_STATS_SECRET or" + + " STREAM_KEY/STREAM_SECRET"); + } + + Properties props = new Properties(); + props.setProperty("io.getstream.chat.apiKey", apiKey); + props.setProperty("io.getstream.chat.apiSecret", apiSecret); + + DefaultClient.setInstance(new DefaultClient(props)); + } + + @Nested + @DisplayName("Basic Queries") + class BasicQueries { + + @Test + @DisplayName("No parameters returns teams") + void noParametersReturnsTeams() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + assertNotNull(response); + assertNotNull(response.getTeams()); + assertTrue(response.getTeams().size() > 0, "Should return at least one team"); + assertNotNull(response.getDuration()); + } + + @Test + @DisplayName("Empty request returns teams") + void emptyRequestReturnsTeams() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + assertNotNull(response.getTeams()); + } + } + + @Nested + @DisplayName("Month Parameter") + class MonthParameter { + + @Test + @DisplayName("Valid month format works") + void validMonthWorks() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().month("2026-02").request(); + + assertNotNull(response.getTeams()); + assertTrue(response.getTeams().size() > 0); + } + + @Test + @DisplayName("Past month with no data returns empty") + void pastMonthReturnsEmpty() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().month("2025-01").request(); + + assertNotNull(response.getTeams()); + assertEquals(0, response.getTeams().size()); + } + + @Test + @DisplayName("Invalid month format throws error") + void invalidMonthThrows() { + assertThrows( + StreamException.class, + () -> TeamUsageStats.queryTeamUsageStats().month("invalid").request()); + } + + @Test + @DisplayName("Wrong length month throws error") + void wrongLengthMonthThrows() { + assertThrows( + StreamException.class, + () -> TeamUsageStats.queryTeamUsageStats().month("2026").request()); + } + } + + @Nested + @DisplayName("Date Range Parameters") + class DateRangeParameters { + + @Test + @DisplayName("Valid date range works") + void validDateRangeWorks() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats() + .startDate("2026-02-01") + .endDate("2026-02-17") + .request(); + + assertNotNull(response.getTeams()); + assertTrue(response.getTeams().size() > 0); + } + + @Test + @DisplayName("Single day range works") + void singleDayRangeWorks() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats() + .startDate("2026-02-17") + .endDate("2026-02-17") + .request(); + + assertNotNull(response.getTeams()); + } + + @Test + @DisplayName("Invalid start_date throws error") + void invalidStartDateThrows() { + assertThrows( + StreamException.class, + () -> TeamUsageStats.queryTeamUsageStats().startDate("bad").request()); + } + + @Test + @DisplayName("end_date before start_date throws error") + void endBeforeStartThrows() { + assertThrows( + StreamException.class, + () -> + TeamUsageStats.queryTeamUsageStats() + .startDate("2026-02-20") + .endDate("2026-02-10") + .request()); + } + } + + @Nested + @DisplayName("Pagination") + class Pagination { + + @Test + @DisplayName("limit=3 returns exactly 3 teams") + void limitReturnsCorrectCount() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().limit(3).request(); + + assertEquals(3, response.getTeams().size()); + } + + @Test + @DisplayName("limit returns next cursor when more data exists") + void limitReturnsNextCursor() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().limit(3).request(); + + assertNotNull(response.getNext()); + assertFalse(response.getNext().isEmpty()); + } + + @Test + @DisplayName("Pagination with next cursor returns different teams") + void paginationReturnsDifferentTeams() throws StreamException { + QueryTeamUsageStatsResponse page1 = TeamUsageStats.queryTeamUsageStats().limit(3).request(); + QueryTeamUsageStatsResponse page2 = + TeamUsageStats.queryTeamUsageStats().limit(3).next(page1.getNext()).request(); + + // Verify no overlap between pages + for (var t1 : page1.getTeams()) { + for (var t2 : page2.getTeams()) { + assertNotEquals( + t1.getTeam(), t2.getTeam(), "Pages should not have overlapping teams"); + } + } + } + + @Test + @DisplayName("limit=30 (max) works") + void maxLimitWorks() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().limit(30).request(); + + assertNotNull(response.getTeams()); + } + + @Test + @DisplayName("limit > 30 throws error") + void overMaxLimitThrows() { + assertThrows( + StreamException.class, () -> TeamUsageStats.queryTeamUsageStats().limit(31).request()); + } + + @Test + @DisplayName("limit + month combined works") + void limitWithMonthWorks() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().limit(2).month("2026-02").request(); + + assertEquals(2, response.getTeams().size()); + } + + @Test + @DisplayName("limit + date range combined works") + void limitWithDateRangeWorks() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats() + .limit(2) + .startDate("2026-02-01") + .endDate("2026-02-17") + .request(); + + assertEquals(2, response.getTeams().size()); + } + } + + @Nested + @DisplayName("Response Structure Validation") + class ResponseStructure { + + @Test + @DisplayName("Response has duration field") + void responseHasDuration() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + assertNotNull(response.getDuration()); + } + + @Test + @DisplayName("Teams have team field") + void teamsHaveTeamField() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + // team field exists (may be empty string for default team) + assertDoesNotThrow(() -> response.getTeams().get(0).getTeam()); + } + + @Test + @DisplayName("All 16 metrics are present and parseable") + void allMetricsPresent() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + var team = response.getTeams().get(0); + + // Daily activity metrics + assertNotNull(team.getUsersDaily(), "users_daily should be present"); + assertNotNull(team.getMessagesDaily(), "messages_daily should be present"); + assertNotNull(team.getTranslationsDaily(), "translations_daily should be present"); + assertNotNull(team.getImageModerationDaily(), "image_moderations_daily should be present"); + + // Peak metrics + assertNotNull(team.getConcurrentUsers(), "concurrent_users should be present"); + assertNotNull(team.getConcurrentConnections(), "concurrent_connections should be present"); + + // Rolling/cumulative metrics + assertNotNull(team.getUsersTotal(), "users_total should be present"); + assertNotNull(team.getUsersLast24Hours(), "users_last_24_hours should be present"); + assertNotNull(team.getUsersLast30Days(), "users_last_30_days should be present"); + assertNotNull(team.getUsersMonthToDate(), "users_month_to_date should be present"); + assertNotNull(team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should be present"); + assertNotNull( + team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should be present"); + assertNotNull(team.getMessagesTotal(), "messages_total should be present"); + assertNotNull(team.getMessagesLast24Hours(), "messages_last_24_hours should be present"); + assertNotNull(team.getMessagesLast30Days(), "messages_last_30_days should be present"); + assertNotNull(team.getMessagesMonthToDate(), "messages_month_to_date should be present"); + } + + @Test + @DisplayName("Metrics have total field with valid value") + void metricsHaveTotal() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + var team = response.getTeams().get(0); + + // Verify total field is present and non-null + assertNotNull(team.getMessagesTotal().getTotal()); + assertNotNull(team.getUsersDaily().getTotal()); + assertNotNull(team.getConcurrentUsers().getTotal()); + } + + @Test + @DisplayName("MetricStats total values are non-negative") + void metricTotalsNonNegative() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + for (var team : response.getTeams()) { + assertTrue(team.getMessagesTotal().getTotal() >= 0, "messages_total should be >= 0"); + assertTrue(team.getUsersDaily().getTotal() >= 0, "users_daily should be >= 0"); + assertTrue(team.getConcurrentUsers().getTotal() >= 0, "concurrent_users should be >= 0"); + } + } + } + + @Nested + @DisplayName("Data Correctness") + class DataCorrectness { + + /** + * Verifies exact metric values for sdk-test-team-1, sdk-test-team-2, sdk-test-team-3. + * + *

This test queries a fixed date range (2026-02-17 to 2026-02-18) and asserts exact values + * for all 16 metrics. This ensures the SDK correctly parses the API response and catches any + * API/SDK regressions. + * + *

Expected values for each sdk-test-team-N: + *

    + *
  • users_daily: 0
  • + *
  • messages_daily: 100
  • + *
  • translations_daily: 0
  • + *
  • image_moderations_daily: 0
  • + *
  • concurrent_users: 0
  • + *
  • concurrent_connections: 0
  • + *
  • users_total: 5
  • + *
  • users_last_24_hours: 5
  • + *
  • users_last_30_days: 5
  • + *
  • users_month_to_date: 5
  • + *
  • users_engaged_last_30_days: 0
  • + *
  • users_engaged_month_to_date: 0
  • + *
  • messages_total: 100
  • + *
  • messages_last_24_hours: 100
  • + *
  • messages_last_30_days: 100
  • + *
  • messages_month_to_date: 100
  • + *
+ */ + @Test + @DisplayName("sdk-test-team-1 has exact expected values for all 16 metrics") + void sdkTestTeam1HasExactValues() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats() + .startDate("2026-02-17") + .endDate("2026-02-18") + .request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-1"); + assertNotNull(team, "sdk-test-team-1 should exist"); + + assertAllMetricsExact(team, "sdk-test-team-1"); + } + + @Test + @DisplayName("sdk-test-team-2 has exact expected values for all 16 metrics") + void sdkTestTeam2HasExactValues() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats() + .startDate("2026-02-17") + .endDate("2026-02-18") + .request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-2"); + assertNotNull(team, "sdk-test-team-2 should exist"); + + assertAllMetricsExact(team, "sdk-test-team-2"); + } + + @Test + @DisplayName("sdk-test-team-3 has exact expected values for all 16 metrics") + void sdkTestTeam3HasExactValues() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats() + .startDate("2026-02-17") + .endDate("2026-02-18") + .request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-3"); + assertNotNull(team, "sdk-test-team-3 should exist"); + + assertAllMetricsExact(team, "sdk-test-team-3"); + } + + private TeamUsageStats findTeamByName(QueryTeamUsageStatsResponse response, String teamName) { + for (TeamUsageStats team : response.getTeams()) { + if (teamName.equals(team.getTeam())) { + return team; + } + } + return null; + } + + private void assertAllMetricsExact(TeamUsageStats team, String teamName) { + // Daily activity metrics + assertEquals(0, team.getUsersDaily().getTotal(), teamName + " users_daily"); + assertEquals(100, team.getMessagesDaily().getTotal(), teamName + " messages_daily"); + assertEquals(0, team.getTranslationsDaily().getTotal(), teamName + " translations_daily"); + assertEquals( + 0, team.getImageModerationDaily().getTotal(), teamName + " image_moderations_daily"); + + // Peak metrics + assertEquals(0, team.getConcurrentUsers().getTotal(), teamName + " concurrent_users"); + assertEquals( + 0, team.getConcurrentConnections().getTotal(), teamName + " concurrent_connections"); + + // User rolling/cumulative metrics + assertEquals(5, team.getUsersTotal().getTotal(), teamName + " users_total"); + assertEquals(5, team.getUsersLast24Hours().getTotal(), teamName + " users_last_24_hours"); + assertEquals(5, team.getUsersLast30Days().getTotal(), teamName + " users_last_30_days"); + assertEquals(5, team.getUsersMonthToDate().getTotal(), teamName + " users_month_to_date"); + assertEquals( + 0, team.getUsersEngagedLast30Days().getTotal(), teamName + " users_engaged_last_30_days"); + assertEquals( + 0, + team.getUsersEngagedMonthToDate().getTotal(), + teamName + " users_engaged_month_to_date"); + + // Message rolling/cumulative metrics + assertEquals(100, team.getMessagesTotal().getTotal(), teamName + " messages_total"); + assertEquals( + 100, team.getMessagesLast24Hours().getTotal(), teamName + " messages_last_24_hours"); + assertEquals( + 100, team.getMessagesLast30Days().getTotal(), teamName + " messages_last_30_days"); + assertEquals( + 100, team.getMessagesMonthToDate().getTotal(), teamName + " messages_month_to_date"); + } + } +} diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java index 254c35a3..95f81892 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java @@ -109,49 +109,49 @@ void whenQueryingTeamUsageStats_thenResponseStructureIsCorrect() { Assertions.assertNotNull(response); Assertions.assertNotNull(response.getTeams()); - // Verify response structure + // Verify response structure - require at least one team to validate assertions List teams = response.getTeams(); - if (!teams.isEmpty()) { - TeamUsageStats team = teams.get(0); - - // Verify all 16 metrics are present - Assertions.assertNotNull(team.getTeam(), "Team identifier should not be null"); - - // Daily activity metrics - Assertions.assertNotNull(team.getUsersDaily(), "users_daily should not be null"); - Assertions.assertNotNull(team.getMessagesDaily(), "messages_daily should not be null"); - Assertions.assertNotNull( - team.getTranslationsDaily(), "translations_daily should not be null"); - Assertions.assertNotNull( - team.getImageModerationDaily(), "image_moderations_daily should not be null"); - - // Peak metrics - Assertions.assertNotNull(team.getConcurrentUsers(), "concurrent_users should not be null"); - Assertions.assertNotNull( - team.getConcurrentConnections(), "concurrent_connections should not be null"); - - // Rolling/cumulative metrics - Assertions.assertNotNull(team.getUsersTotal(), "users_total should not be null"); - Assertions.assertNotNull( - team.getUsersLast24Hours(), "users_last_24_hours should not be null"); - Assertions.assertNotNull(team.getUsersLast30Days(), "users_last_30_days should not be null"); - Assertions.assertNotNull( - team.getUsersMonthToDate(), "users_month_to_date should not be null"); - Assertions.assertNotNull( - team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should not be null"); - Assertions.assertNotNull( - team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should not be null"); - Assertions.assertNotNull(team.getMessagesTotal(), "messages_total should not be null"); - Assertions.assertNotNull( - team.getMessagesLast24Hours(), "messages_last_24_hours should not be null"); - Assertions.assertNotNull( - team.getMessagesLast30Days(), "messages_last_30_days should not be null"); - Assertions.assertNotNull( - team.getMessagesMonthToDate(), "messages_month_to_date should not be null"); - - // Verify MetricStats structure - Assertions.assertNotNull( - team.getUsersDaily().getTotal(), "MetricStats total should not be null"); - } + Assertions.assertFalse( + teams.isEmpty(), + "Expected at least one team in usage stats to verify response structure. " + + "If this test account has no historical data, use a different test account."); + + TeamUsageStats team = teams.get(0); + + // Verify all 16 metrics are present + Assertions.assertNotNull(team.getTeam(), "Team identifier should not be null"); + + // Daily activity metrics + Assertions.assertNotNull(team.getUsersDaily(), "users_daily should not be null"); + Assertions.assertNotNull(team.getMessagesDaily(), "messages_daily should not be null"); + Assertions.assertNotNull(team.getTranslationsDaily(), "translations_daily should not be null"); + Assertions.assertNotNull( + team.getImageModerationDaily(), "image_moderations_daily should not be null"); + + // Peak metrics + Assertions.assertNotNull(team.getConcurrentUsers(), "concurrent_users should not be null"); + Assertions.assertNotNull( + team.getConcurrentConnections(), "concurrent_connections should not be null"); + + // Rolling/cumulative metrics + Assertions.assertNotNull(team.getUsersTotal(), "users_total should not be null"); + Assertions.assertNotNull(team.getUsersLast24Hours(), "users_last_24_hours should not be null"); + Assertions.assertNotNull(team.getUsersLast30Days(), "users_last_30_days should not be null"); + Assertions.assertNotNull(team.getUsersMonthToDate(), "users_month_to_date should not be null"); + Assertions.assertNotNull( + team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should not be null"); + Assertions.assertNotNull( + team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should not be null"); + Assertions.assertNotNull(team.getMessagesTotal(), "messages_total should not be null"); + Assertions.assertNotNull( + team.getMessagesLast24Hours(), "messages_last_24_hours should not be null"); + Assertions.assertNotNull( + team.getMessagesLast30Days(), "messages_last_30_days should not be null"); + Assertions.assertNotNull( + team.getMessagesMonthToDate(), "messages_month_to_date should not be null"); + + // Verify MetricStats structure + Assertions.assertNotNull( + team.getUsersDaily().getTotal(), "MetricStats total should not be null"); } } From 12b3c1d3e60de2edc310f4e7b1c94fbe9dfeb5a9 Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 02:27:47 +0100 Subject: [PATCH 04/10] test: add exact data verification for all query types Expand strict data correctness tests to cover ALL query methods: - Date range query (startDate/endDate) - Month query (month parameter) - No parameters query (default) - Pagination query (limit + next cursor) Each query type verifies exact values for all 16 metrics on sdk-test-team-1, sdk-test-team-2, sdk-test-team-3. Total: 34 integration tests for Team Usage Stats API. Co-Authored-By: Claude Opus 4.5 --- .../java/TeamUsageStatsIntegrationTest.java | 247 +++++++++++++----- 1 file changed, 179 insertions(+), 68 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java index 000d7c3c..d1e1f26c 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java @@ -318,39 +318,25 @@ void metricTotalsNonNegative() throws StreamException { } @Nested - @DisplayName("Data Correctness") - class DataCorrectness { + @DisplayName("Data Correctness - Date Range Query") + class DataCorrectnessDateRange { /** - * Verifies exact metric values for sdk-test-team-1, sdk-test-team-2, sdk-test-team-3. - * - *

This test queries a fixed date range (2026-02-17 to 2026-02-18) and asserts exact values - * for all 16 metrics. This ensures the SDK correctly parses the API response and catches any - * API/SDK regressions. + * Verifies exact metric values for sdk-test-team-1/2/3 using date range query. * *

Expected values for each sdk-test-team-N: *

    - *
  • users_daily: 0
  • - *
  • messages_daily: 100
  • - *
  • translations_daily: 0
  • - *
  • image_moderations_daily: 0
  • - *
  • concurrent_users: 0
  • - *
  • concurrent_connections: 0
  • - *
  • users_total: 5
  • - *
  • users_last_24_hours: 5
  • - *
  • users_last_30_days: 5
  • - *
  • users_month_to_date: 5
  • - *
  • users_engaged_last_30_days: 0
  • - *
  • users_engaged_month_to_date: 0
  • - *
  • messages_total: 100
  • - *
  • messages_last_24_hours: 100
  • - *
  • messages_last_30_days: 100
  • - *
  • messages_month_to_date: 100
  • + *
  • users_daily: 0, messages_daily: 100
  • + *
  • translations_daily: 0, image_moderations_daily: 0
  • + *
  • concurrent_users: 0, concurrent_connections: 0
  • + *
  • users_total: 5, users_last_24_hours: 5, users_last_30_days: 5, users_month_to_date: 5
  • + *
  • users_engaged_last_30_days: 0, users_engaged_month_to_date: 0
  • + *
  • messages_total: 100, messages_last_24_hours: 100, messages_last_30_days: 100, messages_month_to_date: 100
  • *
*/ @Test - @DisplayName("sdk-test-team-1 has exact expected values for all 16 metrics") - void sdkTestTeam1HasExactValues() throws StreamException { + @DisplayName("Date range: sdk-test-team-1 exact values") + void dateRangeSdkTestTeam1() throws StreamException { QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats() .startDate("2026-02-17") @@ -359,13 +345,12 @@ void sdkTestTeam1HasExactValues() throws StreamException { TeamUsageStats team = findTeamByName(response, "sdk-test-team-1"); assertNotNull(team, "sdk-test-team-1 should exist"); - assertAllMetricsExact(team, "sdk-test-team-1"); } @Test - @DisplayName("sdk-test-team-2 has exact expected values for all 16 metrics") - void sdkTestTeam2HasExactValues() throws StreamException { + @DisplayName("Date range: sdk-test-team-2 exact values") + void dateRangeSdkTestTeam2() throws StreamException { QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats() .startDate("2026-02-17") @@ -374,13 +359,12 @@ void sdkTestTeam2HasExactValues() throws StreamException { TeamUsageStats team = findTeamByName(response, "sdk-test-team-2"); assertNotNull(team, "sdk-test-team-2 should exist"); - assertAllMetricsExact(team, "sdk-test-team-2"); } @Test - @DisplayName("sdk-test-team-3 has exact expected values for all 16 metrics") - void sdkTestTeam3HasExactValues() throws StreamException { + @DisplayName("Date range: sdk-test-team-3 exact values") + void dateRangeSdkTestTeam3() throws StreamException { QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats() .startDate("2026-02-17") @@ -389,52 +373,179 @@ void sdkTestTeam3HasExactValues() throws StreamException { TeamUsageStats team = findTeamByName(response, "sdk-test-team-3"); assertNotNull(team, "sdk-test-team-3 should exist"); + assertAllMetricsExact(team, "sdk-test-team-3"); + } + } + + @Nested + @DisplayName("Data Correctness - Month Query") + class DataCorrectnessMonth { + + @Test + @DisplayName("Month query: sdk-test-team-1 exact values") + void monthQuerySdkTestTeam1() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().month("2026-02").request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-1"); + assertNotNull(team, "sdk-test-team-1 should exist"); + assertAllMetricsExact(team, "sdk-test-team-1"); + } + + @Test + @DisplayName("Month query: sdk-test-team-2 exact values") + void monthQuerySdkTestTeam2() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().month("2026-02").request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-2"); + assertNotNull(team, "sdk-test-team-2 should exist"); + assertAllMetricsExact(team, "sdk-test-team-2"); + } + + @Test + @DisplayName("Month query: sdk-test-team-3 exact values") + void monthQuerySdkTestTeam3() throws StreamException { + QueryTeamUsageStatsResponse response = + TeamUsageStats.queryTeamUsageStats().month("2026-02").request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-3"); + assertNotNull(team, "sdk-test-team-3 should exist"); + assertAllMetricsExact(team, "sdk-test-team-3"); + } + } + + @Nested + @DisplayName("Data Correctness - No Parameters Query") + class DataCorrectnessNoParams { + + @Test + @DisplayName("No params: sdk-test-team-1 exact values") + void noParamsSdkTestTeam1() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-1"); + assertNotNull(team, "sdk-test-team-1 should exist"); + assertAllMetricsExact(team, "sdk-test-team-1"); + } + + @Test + @DisplayName("No params: sdk-test-team-2 exact values") + void noParamsSdkTestTeam2() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-2"); + assertNotNull(team, "sdk-test-team-2 should exist"); + assertAllMetricsExact(team, "sdk-test-team-2"); + } + + @Test + @DisplayName("No params: sdk-test-team-3 exact values") + void noParamsSdkTestTeam3() throws StreamException { + QueryTeamUsageStatsResponse response = TeamUsageStats.queryTeamUsageStats().request(); + + TeamUsageStats team = findTeamByName(response, "sdk-test-team-3"); + assertNotNull(team, "sdk-test-team-3 should exist"); + assertAllMetricsExact(team, "sdk-test-team-3"); + } + } + + @Nested + @DisplayName("Data Correctness - Pagination Query") + class DataCorrectnessPagination { + @Test + @DisplayName("Pagination: finds sdk-test-team-1 with exact values across pages") + void paginationFindsSdkTestTeam1() throws StreamException { + TeamUsageStats team = findTeamAcrossPages("sdk-test-team-1"); + assertNotNull(team, "sdk-test-team-1 should exist across paginated results"); + assertAllMetricsExact(team, "sdk-test-team-1"); + } + + @Test + @DisplayName("Pagination: finds sdk-test-team-2 with exact values across pages") + void paginationFindsSdkTestTeam2() throws StreamException { + TeamUsageStats team = findTeamAcrossPages("sdk-test-team-2"); + assertNotNull(team, "sdk-test-team-2 should exist across paginated results"); + assertAllMetricsExact(team, "sdk-test-team-2"); + } + + @Test + @DisplayName("Pagination: finds sdk-test-team-3 with exact values across pages") + void paginationFindsSdkTestTeam3() throws StreamException { + TeamUsageStats team = findTeamAcrossPages("sdk-test-team-3"); + assertNotNull(team, "sdk-test-team-3 should exist across paginated results"); assertAllMetricsExact(team, "sdk-test-team-3"); } - private TeamUsageStats findTeamByName(QueryTeamUsageStatsResponse response, String teamName) { - for (TeamUsageStats team : response.getTeams()) { - if (teamName.equals(team.getTeam())) { - return team; + private TeamUsageStats findTeamAcrossPages(String teamName) throws StreamException { + String nextCursor = null; + int maxPages = 10; // Safety limit + + for (int page = 0; page < maxPages; page++) { + var requestBuilder = TeamUsageStats.queryTeamUsageStats().limit(5); + if (nextCursor != null) { + requestBuilder = requestBuilder.next(nextCursor); + } + + QueryTeamUsageStatsResponse response = requestBuilder.request(); + TeamUsageStats found = findTeamByName(response, teamName); + if (found != null) { + return found; + } + + nextCursor = response.getNext(); + if (nextCursor == null || nextCursor.isEmpty()) { + break; // No more pages } } return null; } + } - private void assertAllMetricsExact(TeamUsageStats team, String teamName) { - // Daily activity metrics - assertEquals(0, team.getUsersDaily().getTotal(), teamName + " users_daily"); - assertEquals(100, team.getMessagesDaily().getTotal(), teamName + " messages_daily"); - assertEquals(0, team.getTranslationsDaily().getTotal(), teamName + " translations_daily"); - assertEquals( - 0, team.getImageModerationDaily().getTotal(), teamName + " image_moderations_daily"); - - // Peak metrics - assertEquals(0, team.getConcurrentUsers().getTotal(), teamName + " concurrent_users"); - assertEquals( - 0, team.getConcurrentConnections().getTotal(), teamName + " concurrent_connections"); - - // User rolling/cumulative metrics - assertEquals(5, team.getUsersTotal().getTotal(), teamName + " users_total"); - assertEquals(5, team.getUsersLast24Hours().getTotal(), teamName + " users_last_24_hours"); - assertEquals(5, team.getUsersLast30Days().getTotal(), teamName + " users_last_30_days"); - assertEquals(5, team.getUsersMonthToDate().getTotal(), teamName + " users_month_to_date"); - assertEquals( - 0, team.getUsersEngagedLast30Days().getTotal(), teamName + " users_engaged_last_30_days"); - assertEquals( - 0, - team.getUsersEngagedMonthToDate().getTotal(), - teamName + " users_engaged_month_to_date"); - - // Message rolling/cumulative metrics - assertEquals(100, team.getMessagesTotal().getTotal(), teamName + " messages_total"); - assertEquals( - 100, team.getMessagesLast24Hours().getTotal(), teamName + " messages_last_24_hours"); - assertEquals( - 100, team.getMessagesLast30Days().getTotal(), teamName + " messages_last_30_days"); - assertEquals( - 100, team.getMessagesMonthToDate().getTotal(), teamName + " messages_month_to_date"); + // Helper methods shared across nested classes + private static TeamUsageStats findTeamByName( + QueryTeamUsageStatsResponse response, String teamName) { + for (TeamUsageStats team : response.getTeams()) { + if (teamName.equals(team.getTeam())) { + return team; + } } + return null; + } + + private static void assertAllMetricsExact(TeamUsageStats team, String teamName) { + // Daily activity metrics + assertEquals(0, team.getUsersDaily().getTotal(), teamName + " users_daily"); + assertEquals(100, team.getMessagesDaily().getTotal(), teamName + " messages_daily"); + assertEquals(0, team.getTranslationsDaily().getTotal(), teamName + " translations_daily"); + assertEquals( + 0, team.getImageModerationDaily().getTotal(), teamName + " image_moderations_daily"); + + // Peak metrics + assertEquals(0, team.getConcurrentUsers().getTotal(), teamName + " concurrent_users"); + assertEquals( + 0, team.getConcurrentConnections().getTotal(), teamName + " concurrent_connections"); + + // User rolling/cumulative metrics + assertEquals(5, team.getUsersTotal().getTotal(), teamName + " users_total"); + assertEquals(5, team.getUsersLast24Hours().getTotal(), teamName + " users_last_24_hours"); + assertEquals(5, team.getUsersLast30Days().getTotal(), teamName + " users_last_30_days"); + assertEquals(5, team.getUsersMonthToDate().getTotal(), teamName + " users_month_to_date"); + assertEquals( + 0, team.getUsersEngagedLast30Days().getTotal(), teamName + " users_engaged_last_30_days"); + assertEquals( + 0, + team.getUsersEngagedMonthToDate().getTotal(), + teamName + " users_engaged_month_to_date"); + + // Message rolling/cumulative metrics + assertEquals(100, team.getMessagesTotal().getTotal(), teamName + " messages_total"); + assertEquals( + 100, team.getMessagesLast24Hours().getTotal(), teamName + " messages_last_24_hours"); + assertEquals( + 100, team.getMessagesLast30Days().getTotal(), teamName + " messages_last_30_days"); + assertEquals( + 100, team.getMessagesMonthToDate().getTotal(), teamName + " messages_month_to_date"); } } From b1c91865897e4e60b97802e2e37db5a6eab5e2aa Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 02:29:50 +0100 Subject: [PATCH 05/10] style: fix spotless formatting Co-Authored-By: Claude Opus 4.5 --- .../java/TeamUsageStatsIntegrationTest.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java index d1e1f26c..0642a003 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java @@ -4,7 +4,6 @@ import io.getstream.chat.java.exceptions.StreamException; import io.getstream.chat.java.models.TeamUsageStats; -import io.getstream.chat.java.models.TeamUsageStats.MetricStats; import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; import io.getstream.chat.java.services.framework.DefaultClient; import java.util.Properties; @@ -196,8 +195,7 @@ void paginationReturnsDifferentTeams() throws StreamException { // Verify no overlap between pages for (var t1 : page1.getTeams()) { for (var t2 : page2.getTeams()) { - assertNotEquals( - t1.getTeam(), t2.getTeam(), "Pages should not have overlapping teams"); + assertNotEquals(t1.getTeam(), t2.getTeam(), "Pages should not have overlapping teams"); } } } @@ -283,7 +281,8 @@ void allMetricsPresent() throws StreamException { assertNotNull(team.getUsersLast24Hours(), "users_last_24_hours should be present"); assertNotNull(team.getUsersLast30Days(), "users_last_30_days should be present"); assertNotNull(team.getUsersMonthToDate(), "users_month_to_date should be present"); - assertNotNull(team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should be present"); + assertNotNull( + team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should be present"); assertNotNull( team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should be present"); assertNotNull(team.getMessagesTotal(), "messages_total should be present"); @@ -325,13 +324,15 @@ class DataCorrectnessDateRange { * Verifies exact metric values for sdk-test-team-1/2/3 using date range query. * *

Expected values for each sdk-test-team-N: + * *

    - *
  • users_daily: 0, messages_daily: 100
  • - *
  • translations_daily: 0, image_moderations_daily: 0
  • - *
  • concurrent_users: 0, concurrent_connections: 0
  • - *
  • users_total: 5, users_last_24_hours: 5, users_last_30_days: 5, users_month_to_date: 5
  • - *
  • users_engaged_last_30_days: 0, users_engaged_month_to_date: 0
  • - *
  • messages_total: 100, messages_last_24_hours: 100, messages_last_30_days: 100, messages_month_to_date: 100
  • + *
  • users_daily: 0, messages_daily: 100 + *
  • translations_daily: 0, image_moderations_daily: 0 + *
  • concurrent_users: 0, concurrent_connections: 0 + *
  • users_total: 5, users_last_24_hours: 5, users_last_30_days: 5, users_month_to_date: 5 + *
  • users_engaged_last_30_days: 0, users_engaged_month_to_date: 0 + *
  • messages_total: 100, messages_last_24_hours: 100, messages_last_30_days: 100, + * messages_month_to_date: 100 *
*/ @Test @@ -535,16 +536,13 @@ private static void assertAllMetricsExact(TeamUsageStats team, String teamName) assertEquals( 0, team.getUsersEngagedLast30Days().getTotal(), teamName + " users_engaged_last_30_days"); assertEquals( - 0, - team.getUsersEngagedMonthToDate().getTotal(), - teamName + " users_engaged_month_to_date"); + 0, team.getUsersEngagedMonthToDate().getTotal(), teamName + " users_engaged_month_to_date"); // Message rolling/cumulative metrics assertEquals(100, team.getMessagesTotal().getTotal(), teamName + " messages_total"); assertEquals( 100, team.getMessagesLast24Hours().getTotal(), teamName + " messages_last_24_hours"); - assertEquals( - 100, team.getMessagesLast30Days().getTotal(), teamName + " messages_last_30_days"); + assertEquals(100, team.getMessagesLast30Days().getTotal(), teamName + " messages_last_30_days"); assertEquals( 100, team.getMessagesMonthToDate().getTotal(), teamName + " messages_month_to_date"); } From 713fb10a6da2e2cf86f64fb101ea390ba6a7ffe0 Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 11:26:28 +0100 Subject: [PATCH 06/10] Use STREAM_MULTI_TENANT_KEY/SECRET for team usage stats tests - Rename env vars from STREAM_USAGE_STATS_* to STREAM_MULTI_TENANT_* - Remove fallback to STREAM_KEY/STREAM_SECRET - Fail explicitly with clear error message when credentials are missing Co-Authored-By: Claude Opus 4.5 --- .../java/TeamUsageStatsIntegrationTest.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java index 0642a003..f65aea54 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java @@ -13,8 +13,8 @@ import org.junit.jupiter.api.Test; /** - * Integration tests for Team Usage Stats API. Uses dedicated test credentials from - * STREAM_USAGE_STATS_KEY and STREAM_USAGE_STATS_SECRET environment variables. + * Integration tests for Team Usage Stats API. Uses dedicated multi-tenant test app credentials from + * STREAM_MULTI_TENANT_KEY and STREAM_MULTI_TENANT_SECRET environment variables. * *

These tests verify that the SDK correctly parses all response data from the backend. */ @@ -22,22 +22,13 @@ public class TeamUsageStatsIntegrationTest { @BeforeAll static void setup() { - // Use dedicated credentials for usage stats testing - String apiKey = System.getenv("STREAM_USAGE_STATS_KEY"); - String apiSecret = System.getenv("STREAM_USAGE_STATS_SECRET"); + String apiKey = System.getenv("STREAM_MULTI_TENANT_KEY"); + String apiSecret = System.getenv("STREAM_MULTI_TENANT_SECRET"); - // Fall back to standard credentials if usage stats specific ones aren't set - if (apiKey == null || apiKey.isEmpty()) { - apiKey = System.getenv("STREAM_KEY"); - } - if (apiSecret == null || apiSecret.isEmpty()) { - apiSecret = System.getenv("STREAM_SECRET"); - } - - if (apiKey == null || apiSecret == null) { + if (apiKey == null || apiKey.isEmpty() || apiSecret == null || apiSecret.isEmpty()) { throw new IllegalStateException( - "Missing credentials. Set STREAM_USAGE_STATS_KEY/STREAM_USAGE_STATS_SECRET or" - + " STREAM_KEY/STREAM_SECRET"); + "Multi-tenant test app credentials are missing. " + + "Set STREAM_MULTI_TENANT_KEY and STREAM_MULTI_TENANT_SECRET environment variables."); } Properties props = new Properties(); From 0b627fe6be2c41bed56cc0498c84a6d298f89c3a Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 12:27:27 +0100 Subject: [PATCH 07/10] Fix CI failures for team usage stats tests - Add STREAM_MULTI_TENANT_KEY/SECRET env vars to CI workflow - Fix TeamUsageStatsTest to not require data when account has no multi-tenant data (strict data verification is in TeamUsageStatsIntegrationTest) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/ci.yml | 2 + .../chat/java/TeamUsageStatsTest.java | 89 ++++++++++--------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48819368..a6a87b1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,8 @@ jobs: env: STREAM_KEY: ${{ secrets.STREAM_KEY }} STREAM_SECRET: ${{ secrets.STREAM_SECRET }} + STREAM_MULTI_TENANT_KEY: ${{ secrets.STREAM_MULTI_TENANT_KEY }} + STREAM_MULTI_TENANT_SECRET: ${{ secrets.STREAM_MULTI_TENANT_SECRET }} run: | ./gradlew spotlessCheck --no-daemon ./gradlew javadoc --no-daemon diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java index 95f81892..736ad931 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java @@ -109,49 +109,52 @@ void whenQueryingTeamUsageStats_thenResponseStructureIsCorrect() { Assertions.assertNotNull(response); Assertions.assertNotNull(response.getTeams()); - // Verify response structure - require at least one team to validate assertions + // If there are teams with data, verify response structure + // Note: The regular test account may not have multi-tenant data, so we only + // verify structure when data is present. Full data verification is done in + // TeamUsageStatsIntegrationTest with dedicated multi-tenant credentials. List teams = response.getTeams(); - Assertions.assertFalse( - teams.isEmpty(), - "Expected at least one team in usage stats to verify response structure. " - + "If this test account has no historical data, use a different test account."); - - TeamUsageStats team = teams.get(0); - - // Verify all 16 metrics are present - Assertions.assertNotNull(team.getTeam(), "Team identifier should not be null"); - - // Daily activity metrics - Assertions.assertNotNull(team.getUsersDaily(), "users_daily should not be null"); - Assertions.assertNotNull(team.getMessagesDaily(), "messages_daily should not be null"); - Assertions.assertNotNull(team.getTranslationsDaily(), "translations_daily should not be null"); - Assertions.assertNotNull( - team.getImageModerationDaily(), "image_moderations_daily should not be null"); - - // Peak metrics - Assertions.assertNotNull(team.getConcurrentUsers(), "concurrent_users should not be null"); - Assertions.assertNotNull( - team.getConcurrentConnections(), "concurrent_connections should not be null"); - - // Rolling/cumulative metrics - Assertions.assertNotNull(team.getUsersTotal(), "users_total should not be null"); - Assertions.assertNotNull(team.getUsersLast24Hours(), "users_last_24_hours should not be null"); - Assertions.assertNotNull(team.getUsersLast30Days(), "users_last_30_days should not be null"); - Assertions.assertNotNull(team.getUsersMonthToDate(), "users_month_to_date should not be null"); - Assertions.assertNotNull( - team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should not be null"); - Assertions.assertNotNull( - team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should not be null"); - Assertions.assertNotNull(team.getMessagesTotal(), "messages_total should not be null"); - Assertions.assertNotNull( - team.getMessagesLast24Hours(), "messages_last_24_hours should not be null"); - Assertions.assertNotNull( - team.getMessagesLast30Days(), "messages_last_30_days should not be null"); - Assertions.assertNotNull( - team.getMessagesMonthToDate(), "messages_month_to_date should not be null"); - - // Verify MetricStats structure - Assertions.assertNotNull( - team.getUsersDaily().getTotal(), "MetricStats total should not be null"); + if (!teams.isEmpty()) { + TeamUsageStats team = teams.get(0); + + // Verify all 16 metrics are present + Assertions.assertNotNull(team.getTeam(), "Team identifier should not be null"); + + // Daily activity metrics + Assertions.assertNotNull(team.getUsersDaily(), "users_daily should not be null"); + Assertions.assertNotNull(team.getMessagesDaily(), "messages_daily should not be null"); + Assertions.assertNotNull( + team.getTranslationsDaily(), "translations_daily should not be null"); + Assertions.assertNotNull( + team.getImageModerationDaily(), "image_moderations_daily should not be null"); + + // Peak metrics + Assertions.assertNotNull(team.getConcurrentUsers(), "concurrent_users should not be null"); + Assertions.assertNotNull( + team.getConcurrentConnections(), "concurrent_connections should not be null"); + + // Rolling/cumulative metrics + Assertions.assertNotNull(team.getUsersTotal(), "users_total should not be null"); + Assertions.assertNotNull( + team.getUsersLast24Hours(), "users_last_24_hours should not be null"); + Assertions.assertNotNull(team.getUsersLast30Days(), "users_last_30_days should not be null"); + Assertions.assertNotNull( + team.getUsersMonthToDate(), "users_month_to_date should not be null"); + Assertions.assertNotNull( + team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should not be null"); + Assertions.assertNotNull( + team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should not be null"); + Assertions.assertNotNull(team.getMessagesTotal(), "messages_total should not be null"); + Assertions.assertNotNull( + team.getMessagesLast24Hours(), "messages_last_24_hours should not be null"); + Assertions.assertNotNull( + team.getMessagesLast30Days(), "messages_last_30_days should not be null"); + Assertions.assertNotNull( + team.getMessagesMonthToDate(), "messages_month_to_date should not be null"); + + // Verify MetricStats structure + Assertions.assertNotNull( + team.getUsersDaily().getTotal(), "MetricStats total should not be null"); + } } } From d554bf064810e46f6463fc5167b4671f1e818384 Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 13:38:55 +0100 Subject: [PATCH 08/10] fix: restore original client after TeamUsageStatsIntegrationTest The test was setting the global DefaultClient singleton to use multi-tenant credentials but never restoring it. This caused subsequent tests (like UserTest.canCreateGuestUser) to fail because guest access is disabled for multi-tenant apps. Co-Authored-By: Claude Opus 4.5 --- .../chat/java/TeamUsageStatsIntegrationTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java index f65aea54..eeb51fd7 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsIntegrationTest.java @@ -7,6 +7,7 @@ import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; import io.getstream.chat.java.services.framework.DefaultClient; import java.util.Properties; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -20,8 +21,13 @@ */ public class TeamUsageStatsIntegrationTest { + private static DefaultClient originalClient; + @BeforeAll static void setup() { + // Save the original client to restore after tests + originalClient = DefaultClient.getInstance(); + String apiKey = System.getenv("STREAM_MULTI_TENANT_KEY"); String apiSecret = System.getenv("STREAM_MULTI_TENANT_SECRET"); @@ -38,6 +44,14 @@ static void setup() { DefaultClient.setInstance(new DefaultClient(props)); } + @AfterAll + static void teardown() { + // Restore the original client so other tests use the correct credentials + if (originalClient != null) { + DefaultClient.setInstance(originalClient); + } + } + @Nested @DisplayName("Basic Queries") class BasicQueries { From 5a27792e280b59d22a22391bae844fbf8e79d01f Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 15:08:50 +0100 Subject: [PATCH 09/10] refactor: simplify TeamUsageStatsTest for non-multi-tenant app The regular test app doesn't have multi-tenant enabled, so teams will always be empty. Removed dead code inside if-blocks that would never execute and added explicit assertions that teams is empty. Full data verification is done in TeamUsageStatsIntegrationTest with dedicated multi-tenant credentials. Co-Authored-By: Claude Opus 4.5 --- .../chat/java/TeamUsageStatsTest.java | 111 +++--------------- 1 file changed, 14 insertions(+), 97 deletions(-) diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java index 736ad931..87e45633 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java @@ -1,14 +1,18 @@ package io.getstream.chat.java; -import io.getstream.chat.java.models.TeamUsageStats; import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; +import io.getstream.chat.java.models.TeamUsageStats; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +/** + * Basic tests for Team Usage Stats API using regular (non-multi-tenant) app credentials. + * Since the regular app doesn't have multi-tenant enabled, teams will always be empty. + * Full data verification is done in TeamUsageStatsIntegrationTest with multi-tenant credentials. + */ public class TeamUsageStatsTest { @DisplayName("Can query team usage stats with default options") @@ -19,13 +23,13 @@ void whenQueryingTeamUsageStatsWithDefaultOptions_thenNoException() { Assertions.assertNotNull(response); Assertions.assertNotNull(response.getTeams()); - // Teams list might be empty if there's no usage data + // Regular app doesn't have multi-tenant, so teams is empty + Assertions.assertTrue(response.getTeams().isEmpty()); } @DisplayName("Can query team usage stats with month parameter") @Test void whenQueryingTeamUsageStatsWithMonth_thenNoException() { - // Use current month String currentMonth = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")); QueryTeamUsageStatsResponse response = @@ -34,12 +38,12 @@ void whenQueryingTeamUsageStatsWithMonth_thenNoException() { Assertions.assertNotNull(response); Assertions.assertNotNull(response.getTeams()); + Assertions.assertTrue(response.getTeams().isEmpty()); } @DisplayName("Can query team usage stats with date range") @Test void whenQueryingTeamUsageStatsWithDateRange_thenNoException() { - // Use last 7 days LocalDate endDate = LocalDate.now(); LocalDate startDate = endDate.minusDays(7); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @@ -54,107 +58,20 @@ void whenQueryingTeamUsageStatsWithDateRange_thenNoException() { Assertions.assertNotNull(response); Assertions.assertNotNull(response.getTeams()); - - // If there are teams with data, verify the daily breakdown is present - if (!response.getTeams().isEmpty()) { - TeamUsageStats team = response.getTeams().get(0); - Assertions.assertNotNull(team.getTeam()); - Assertions.assertNotNull(team.getUsersDaily()); - Assertions.assertNotNull(team.getMessagesDaily()); - } + Assertions.assertTrue(response.getTeams().isEmpty()); } @DisplayName("Can query team usage stats with pagination") @Test void whenQueryingTeamUsageStatsWithPagination_thenNoException() { - // First page with limit - QueryTeamUsageStatsResponse firstPage = - Assertions.assertDoesNotThrow( - () -> TeamUsageStats.queryTeamUsageStats().limit(10).request()); - - Assertions.assertNotNull(firstPage); - Assertions.assertNotNull(firstPage.getTeams()); - - // If there's a next cursor, fetch the next page - if (firstPage.getNext() != null && !firstPage.getNext().isEmpty()) { - QueryTeamUsageStatsResponse secondPage = - Assertions.assertDoesNotThrow( - () -> - TeamUsageStats.queryTeamUsageStats() - .limit(10) - .next(firstPage.getNext()) - .request()); - - Assertions.assertNotNull(secondPage); - Assertions.assertNotNull(secondPage.getTeams()); - } - } - - @DisplayName("Can query team usage stats for last year and verify response structure") - @Test - void whenQueryingTeamUsageStats_thenResponseStructureIsCorrect() { - // Query last year to maximize chance of getting data - LocalDate endDate = LocalDate.now(); - LocalDate startDate = endDate.minusYears(1); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - QueryTeamUsageStatsResponse response = Assertions.assertDoesNotThrow( - () -> - TeamUsageStats.queryTeamUsageStats() - .startDate(startDate.format(formatter)) - .endDate(endDate.format(formatter)) - .request()); + () -> TeamUsageStats.queryTeamUsageStats().limit(10).request()); Assertions.assertNotNull(response); Assertions.assertNotNull(response.getTeams()); - - // If there are teams with data, verify response structure - // Note: The regular test account may not have multi-tenant data, so we only - // verify structure when data is present. Full data verification is done in - // TeamUsageStatsIntegrationTest with dedicated multi-tenant credentials. - List teams = response.getTeams(); - if (!teams.isEmpty()) { - TeamUsageStats team = teams.get(0); - - // Verify all 16 metrics are present - Assertions.assertNotNull(team.getTeam(), "Team identifier should not be null"); - - // Daily activity metrics - Assertions.assertNotNull(team.getUsersDaily(), "users_daily should not be null"); - Assertions.assertNotNull(team.getMessagesDaily(), "messages_daily should not be null"); - Assertions.assertNotNull( - team.getTranslationsDaily(), "translations_daily should not be null"); - Assertions.assertNotNull( - team.getImageModerationDaily(), "image_moderations_daily should not be null"); - - // Peak metrics - Assertions.assertNotNull(team.getConcurrentUsers(), "concurrent_users should not be null"); - Assertions.assertNotNull( - team.getConcurrentConnections(), "concurrent_connections should not be null"); - - // Rolling/cumulative metrics - Assertions.assertNotNull(team.getUsersTotal(), "users_total should not be null"); - Assertions.assertNotNull( - team.getUsersLast24Hours(), "users_last_24_hours should not be null"); - Assertions.assertNotNull(team.getUsersLast30Days(), "users_last_30_days should not be null"); - Assertions.assertNotNull( - team.getUsersMonthToDate(), "users_month_to_date should not be null"); - Assertions.assertNotNull( - team.getUsersEngagedLast30Days(), "users_engaged_last_30_days should not be null"); - Assertions.assertNotNull( - team.getUsersEngagedMonthToDate(), "users_engaged_month_to_date should not be null"); - Assertions.assertNotNull(team.getMessagesTotal(), "messages_total should not be null"); - Assertions.assertNotNull( - team.getMessagesLast24Hours(), "messages_last_24_hours should not be null"); - Assertions.assertNotNull( - team.getMessagesLast30Days(), "messages_last_30_days should not be null"); - Assertions.assertNotNull( - team.getMessagesMonthToDate(), "messages_month_to_date should not be null"); - - // Verify MetricStats structure - Assertions.assertNotNull( - team.getUsersDaily().getTotal(), "MetricStats total should not be null"); - } + Assertions.assertTrue(response.getTeams().isEmpty()); + // No next cursor when teams is empty + Assertions.assertTrue(response.getNext() == null || response.getNext().isEmpty()); } } From 4a2fdcb6d78645c36bf36ca08924ab816dcce77b Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 15:10:27 +0100 Subject: [PATCH 10/10] resolve comments: remove hardcoded metric count, simplify tests - Remove "16 metrics" from javadoc (implementation detail) - Simplify TeamUsageStatsTest for non-multi-tenant app - Restore original client after TeamUsageStatsIntegrationTest Co-Authored-By: Claude Opus 4.5 --- .../io/getstream/chat/java/models/TeamUsageStats.java | 2 +- .../java/io/getstream/chat/java/TeamUsageStatsTest.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java b/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java index 53df0a9b..aca81314 100644 --- a/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java +++ b/src/main/java/io/getstream/chat/java/models/TeamUsageStats.java @@ -212,7 +212,7 @@ public static class QueryTeamUsageStatsResponse extends StreamResponseObject { /** * Queries team-level usage statistics from the warehouse database. * - *

Returns all 16 metrics grouped by team with cursor-based pagination. + *

Returns usage metrics grouped by team with cursor-based pagination. * *

Date Range Options (mutually exclusive): * diff --git a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java index 87e45633..09ab901e 100644 --- a/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java +++ b/src/test/java/io/getstream/chat/java/TeamUsageStatsTest.java @@ -1,7 +1,7 @@ package io.getstream.chat.java; -import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; import io.getstream.chat.java.models.TeamUsageStats; +import io.getstream.chat.java.models.TeamUsageStats.QueryTeamUsageStatsResponse; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import org.junit.jupiter.api.Assertions; @@ -9,9 +9,9 @@ import org.junit.jupiter.api.Test; /** - * Basic tests for Team Usage Stats API using regular (non-multi-tenant) app credentials. - * Since the regular app doesn't have multi-tenant enabled, teams will always be empty. - * Full data verification is done in TeamUsageStatsIntegrationTest with multi-tenant credentials. + * Basic tests for Team Usage Stats API using regular (non-multi-tenant) app credentials. Since the + * regular app doesn't have multi-tenant enabled, teams will always be empty. Full data verification + * is done in TeamUsageStatsIntegrationTest with multi-tenant credentials. */ public class TeamUsageStatsTest {