From 391dc502c89467875d5c5cc0c1451d7e9e235715 Mon Sep 17 00:00:00 2001 From: nijeeshjoshy Date: Tue, 12 May 2026 18:10:40 +0200 Subject: [PATCH] chore(tests): clean leftover test state on shared CI app Three test classes routinely fail on CI because the shared test app accumulates state across runs whenever a prior PR's job died before its inline teardown could fire: - ChannelTypeTest / CommandTest: hit the per-app 50-item cap on custom channel types and custom commands (CustomChannelTypeLimit / CustomCommandLimit on the backend). Add @BeforeAll sweeps that list and best-effort delete the non-system entries. - UserTest: User.queryBanned() returns a paginated slice, so once enough zombie bans pile up, the just-created ban under test ends up past the first page and the 'assertTrue(stream.anyMatch...)' check fails. Add @BeforeAll cleanup that pages through and unbans each. - TaskStatusTest: the default 5s waitFor was routinely too short for Channel.deleteMany under load. Bump that one test to 5 minutes. All sweeps are best-effort: anything still in use just returns an error from delete()/unban() and is skipped. --- .../getstream/chat/java/ChannelTypeTest.java | 30 +++++++++++++++ .../io/getstream/chat/java/CommandTest.java | 31 +++++++++++++++ .../getstream/chat/java/TaskStatusTest.java | 7 +++- .../java/io/getstream/chat/java/UserTest.java | 38 +++++++++++++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/getstream/chat/java/ChannelTypeTest.java b/src/test/java/io/getstream/chat/java/ChannelTypeTest.java index bd65914d3..10d14c61d 100644 --- a/src/test/java/io/getstream/chat/java/ChannelTypeTest.java +++ b/src/test/java/io/getstream/chat/java/ChannelTypeTest.java @@ -15,6 +15,36 @@ import org.junit.jupiter.api.*; public class ChannelTypeTest extends BasicTest { + + private static final java.util.Set SYSTEM_CHANNEL_TYPES = + java.util.Set.of("messaging", "livestream", "gaming", "commerce", "team"); + + /** + * Delete zombie custom channel types left over from prior CI runs that failed mid-test before + * their inline {@code ChannelType.delete(...)} could fire. The shared test app caps custom + * channel types at 50; without this sweep new branches hit the quota as soon as enough zombie + * types accumulate. Only deletes types not in {@link #SYSTEM_CHANNEL_TYPES}. + */ + @BeforeAll + static void cleanupLeftoverChannelTypes() { + try { + var resp = ChannelType.list().request(); + if (resp == null || resp.getChannelTypes() == null) return; + resp.getChannelTypes().keySet().stream() + .filter(name -> !SYSTEM_CHANNEL_TYPES.contains(name)) + .forEach( + name -> { + try { + ChannelType.delete(name).request(); + } catch (StreamException ignored) { + // Best-effort: skip types that are in use or already deleted. + } + }); + } catch (StreamException ignored) { + // Best-effort: if list fails the tests below will surface the real error. + } + } + @Test @DisplayName("Get channel type with populated commands") void whenPopulatingCommands_thenFetchChannelTypeWithoutAnyIssues() { diff --git a/src/test/java/io/getstream/chat/java/CommandTest.java b/src/test/java/io/getstream/chat/java/CommandTest.java index 10b9757d2..7abbabb25 100644 --- a/src/test/java/io/getstream/chat/java/CommandTest.java +++ b/src/test/java/io/getstream/chat/java/CommandTest.java @@ -1,14 +1,45 @@ package io.getstream.chat.java; +import io.getstream.chat.java.exceptions.StreamException; import io.getstream.chat.java.models.Command; import java.util.List; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; public class CommandTest extends BasicTest { + private static final java.util.Set SYSTEM_COMMANDS = + java.util.Set.of("giphy", "imgur", "ban", "unban", "mute", "unmute", "flag", "unflag"); + + /** + * Delete zombie custom commands left over from prior CI runs that failed mid-test before the + * inline {@code Command.delete(...)} could fire. The shared test app caps custom commands at 50. + * Only deletes commands not in {@link #SYSTEM_COMMANDS}. + */ + @BeforeAll + static void cleanupLeftoverCommands() { + try { + var resp = Command.list().request(); + if (resp == null || resp.getCommands() == null) return; + resp.getCommands().stream() + .map(Command::getName) + .filter(name -> name != null && !SYSTEM_COMMANDS.contains(name)) + .forEach( + name -> { + try { + Command.delete(name).request(); + } catch (StreamException ignored) { + // Best-effort: skip commands that are in use or already deleted. + } + }); + } catch (StreamException ignored) { + // Best-effort. + } + } + @DisplayName("Can create command") @Test void whenCreatingCommand_thenCorrectDescription() { diff --git a/src/test/java/io/getstream/chat/java/TaskStatusTest.java b/src/test/java/io/getstream/chat/java/TaskStatusTest.java index 9a0ab93e5..371783ec9 100644 --- a/src/test/java/io/getstream/chat/java/TaskStatusTest.java +++ b/src/test/java/io/getstream/chat/java/TaskStatusTest.java @@ -20,12 +20,17 @@ void whenTaskHasBeenExecuted_thenGetItById() { var cids = List.of(ch1.getCId(), ch2.getCId()); var taskId = Channel.deleteMany(cids).request().getTaskId(); + // Shared CI test app: asynq queue can be backed up under load — the + // default 5s waitFor is routinely too short. 5 minutes leaves real + // headroom without masking a stuck task. waitFor( () -> { var taskStatusResponse = Assertions.assertDoesNotThrow(() -> TaskStatus.get(taskId).request()); return "completed".equals(taskStatusResponse.getStatus()); - }); + }, + 1000L, + 300000L); }); } } diff --git a/src/test/java/io/getstream/chat/java/UserTest.java b/src/test/java/io/getstream/chat/java/UserTest.java index e212fde6a..84e722c81 100644 --- a/src/test/java/io/getstream/chat/java/UserTest.java +++ b/src/test/java/io/getstream/chat/java/UserTest.java @@ -23,12 +23,50 @@ import java.util.logging.Logger; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.ThrowingSupplier; public class UserTest extends BasicTest { + /** + * Clear zombie bans left over from prior CI runs that died before their cleanup could fire. + * {@code User.queryBanned().request()} returns a paginated slice; once enough bans accumulate on + * the shared test app, the just-created ban under test ends up past the first page and the {@code + * assertTrue(bans.stream().anyMatch(...))} assertion fails. Best-effort sweep. + */ + @BeforeAll + static void cleanupLeftoverBans() { + // queryBanned() returns a paginated slice, so a single pass only clears + // the first page. Loop until either the response is empty or we stop + // making progress; cap iterations to avoid running forever against a + // poisoned app. + Set seen = new HashSet<>(); + for (int round = 0; round < 20; round++) { + List bans; + try { + bans = User.queryBanned().request().getBans(); + } catch (StreamException ignored) { + return; // Best-effort. + } + if (bans == null || bans.isEmpty()) return; + int unbannedThisRound = 0; + for (var ban : bans) { + if (ban.getUser() == null || ban.getUser().getId() == null) continue; + String id = ban.getUser().getId(); + if (!seen.add(id)) continue; + try { + User.unban(id).request(); + unbannedThisRound++; + } catch (StreamException ignored) { + // In-use or already-deleted; skip. + } + } + if (unbannedThisRound == 0) return; // No progress; bail. + } + } + @DisplayName("Can list users with no Exception") @Test void whenListingUsers_thenNoException() {