Skip to content

Commit aa493bf

Browse files
chore(tests): clean leftover test state on shared CI app (#256)
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.
1 parent 027a20a commit aa493bf

4 files changed

Lines changed: 105 additions & 1 deletion

File tree

src/test/java/io/getstream/chat/java/ChannelTypeTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,36 @@
1515
import org.junit.jupiter.api.*;
1616

1717
public class ChannelTypeTest extends BasicTest {
18+
19+
private static final java.util.Set<String> SYSTEM_CHANNEL_TYPES =
20+
java.util.Set.of("messaging", "livestream", "gaming", "commerce", "team");
21+
22+
/**
23+
* Delete zombie custom channel types left over from prior CI runs that failed mid-test before
24+
* their inline {@code ChannelType.delete(...)} could fire. The shared test app caps custom
25+
* channel types at 50; without this sweep new branches hit the quota as soon as enough zombie
26+
* types accumulate. Only deletes types not in {@link #SYSTEM_CHANNEL_TYPES}.
27+
*/
28+
@BeforeAll
29+
static void cleanupLeftoverChannelTypes() {
30+
try {
31+
var resp = ChannelType.list().request();
32+
if (resp == null || resp.getChannelTypes() == null) return;
33+
resp.getChannelTypes().keySet().stream()
34+
.filter(name -> !SYSTEM_CHANNEL_TYPES.contains(name))
35+
.forEach(
36+
name -> {
37+
try {
38+
ChannelType.delete(name).request();
39+
} catch (StreamException ignored) {
40+
// Best-effort: skip types that are in use or already deleted.
41+
}
42+
});
43+
} catch (StreamException ignored) {
44+
// Best-effort: if list fails the tests below will surface the real error.
45+
}
46+
}
47+
1848
@Test
1949
@DisplayName("Get channel type with populated commands")
2050
void whenPopulatingCommands_thenFetchChannelTypeWithoutAnyIssues() {

src/test/java/io/getstream/chat/java/CommandTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
11
package io.getstream.chat.java;
22

3+
import io.getstream.chat.java.exceptions.StreamException;
34
import io.getstream.chat.java.models.Command;
45
import java.util.List;
56
import org.apache.commons.lang3.RandomStringUtils;
67
import org.junit.jupiter.api.Assertions;
8+
import org.junit.jupiter.api.BeforeAll;
79
import org.junit.jupiter.api.DisplayName;
810
import org.junit.jupiter.api.Test;
911

1012
public class CommandTest extends BasicTest {
1113

14+
private static final java.util.Set<String> SYSTEM_COMMANDS =
15+
java.util.Set.of("giphy", "imgur", "ban", "unban", "mute", "unmute", "flag", "unflag");
16+
17+
/**
18+
* Delete zombie custom commands left over from prior CI runs that failed mid-test before the
19+
* inline {@code Command.delete(...)} could fire. The shared test app caps custom commands at 50.
20+
* Only deletes commands not in {@link #SYSTEM_COMMANDS}.
21+
*/
22+
@BeforeAll
23+
static void cleanupLeftoverCommands() {
24+
try {
25+
var resp = Command.list().request();
26+
if (resp == null || resp.getCommands() == null) return;
27+
resp.getCommands().stream()
28+
.map(Command::getName)
29+
.filter(name -> name != null && !SYSTEM_COMMANDS.contains(name))
30+
.forEach(
31+
name -> {
32+
try {
33+
Command.delete(name).request();
34+
} catch (StreamException ignored) {
35+
// Best-effort: skip commands that are in use or already deleted.
36+
}
37+
});
38+
} catch (StreamException ignored) {
39+
// Best-effort.
40+
}
41+
}
42+
1243
@DisplayName("Can create command")
1344
@Test
1445
void whenCreatingCommand_thenCorrectDescription() {

src/test/java/io/getstream/chat/java/TaskStatusTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ void whenTaskHasBeenExecuted_thenGetItById() {
2020

2121
var cids = List.of(ch1.getCId(), ch2.getCId());
2222
var taskId = Channel.deleteMany(cids).request().getTaskId();
23+
// Shared CI test app: asynq queue can be backed up under load — the
24+
// default 5s waitFor is routinely too short. 5 minutes leaves real
25+
// headroom without masking a stuck task.
2326
waitFor(
2427
() -> {
2528
var taskStatusResponse =
2629
Assertions.assertDoesNotThrow(() -> TaskStatus.get(taskId).request());
2730
return "completed".equals(taskStatusResponse.getStatus());
28-
});
31+
},
32+
1000L,
33+
300000L);
2934
});
3035
}
3136
}

src/test/java/io/getstream/chat/java/UserTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,50 @@
2323
import java.util.logging.Logger;
2424
import org.apache.commons.lang3.RandomStringUtils;
2525
import org.junit.jupiter.api.Assertions;
26+
import org.junit.jupiter.api.BeforeAll;
2627
import org.junit.jupiter.api.DisplayName;
2728
import org.junit.jupiter.api.Test;
2829
import org.junit.jupiter.api.function.ThrowingSupplier;
2930

3031
public class UserTest extends BasicTest {
3132

33+
/**
34+
* Clear zombie bans left over from prior CI runs that died before their cleanup could fire.
35+
* {@code User.queryBanned().request()} returns a paginated slice; once enough bans accumulate on
36+
* the shared test app, the just-created ban under test ends up past the first page and the {@code
37+
* assertTrue(bans.stream().anyMatch(...))} assertion fails. Best-effort sweep.
38+
*/
39+
@BeforeAll
40+
static void cleanupLeftoverBans() {
41+
// queryBanned() returns a paginated slice, so a single pass only clears
42+
// the first page. Loop until either the response is empty or we stop
43+
// making progress; cap iterations to avoid running forever against a
44+
// poisoned app.
45+
Set<String> seen = new HashSet<>();
46+
for (int round = 0; round < 20; round++) {
47+
List<Ban> bans;
48+
try {
49+
bans = User.queryBanned().request().getBans();
50+
} catch (StreamException ignored) {
51+
return; // Best-effort.
52+
}
53+
if (bans == null || bans.isEmpty()) return;
54+
int unbannedThisRound = 0;
55+
for (var ban : bans) {
56+
if (ban.getUser() == null || ban.getUser().getId() == null) continue;
57+
String id = ban.getUser().getId();
58+
if (!seen.add(id)) continue;
59+
try {
60+
User.unban(id).request();
61+
unbannedThisRound++;
62+
} catch (StreamException ignored) {
63+
// In-use or already-deleted; skip.
64+
}
65+
}
66+
if (unbannedThisRound == 0) return; // No progress; bail.
67+
}
68+
}
69+
3270
@DisplayName("Can list users with no Exception")
3371
@Test
3472
void whenListingUsers_thenNoException() {

0 commit comments

Comments
 (0)