fix(events): reject malformed pagination cursors as 400 errors
Wraps DateTimeParseException from Instant.parse in IllegalArgumentException so the controller maps it to 400. Also rejects cursors with empty instance_id (trailing '|') which would otherwise produce a vacuous keyset predicate.
This commit is contained in:
@@ -80,10 +80,15 @@ public class ClickHouseAgentEventRepository implements AgentEventRepository {
|
|||||||
if (cursor != null && !cursor.isEmpty()) {
|
if (cursor != null && !cursor.isEmpty()) {
|
||||||
String decoded = new String(Base64.getUrlDecoder().decode(cursor), StandardCharsets.UTF_8);
|
String decoded = new String(Base64.getUrlDecoder().decode(cursor), StandardCharsets.UTF_8);
|
||||||
int bar = decoded.indexOf('|');
|
int bar = decoded.indexOf('|');
|
||||||
if (bar <= 0) {
|
if (bar <= 0 || bar == decoded.length() - 1) {
|
||||||
throw new IllegalArgumentException("Malformed cursor");
|
throw new IllegalArgumentException("Malformed cursor");
|
||||||
}
|
}
|
||||||
Instant cursorTs = Instant.parse(decoded.substring(0, bar));
|
Instant cursorTs;
|
||||||
|
try {
|
||||||
|
cursorTs = Instant.parse(decoded.substring(0, bar));
|
||||||
|
} catch (java.time.format.DateTimeParseException e) {
|
||||||
|
throw new IllegalArgumentException("Malformed cursor", e);
|
||||||
|
}
|
||||||
String cursorInstance = decoded.substring(bar + 1);
|
String cursorInstance = decoded.substring(bar + 1);
|
||||||
sql.append(" AND (timestamp < ? OR (timestamp = ? AND instance_id > ?))");
|
sql.append(" AND (timestamp < ? OR (timestamp = ? AND instance_id > ?))");
|
||||||
params.add(Timestamp.from(cursorTs));
|
params.add(Timestamp.from(cursorTs));
|
||||||
|
|||||||
@@ -227,4 +227,26 @@ class ClickHouseAgentEventRepositoryIT {
|
|||||||
assertThat(p2.data().get(0).instanceId()).isEqualTo("agent-z");
|
assertThat(p2.data().get(0).instanceId()).isEqualTo("agent-z");
|
||||||
assertThat(p2.hasMore()).isFalse();
|
assertThat(p2.hasMore()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queryPage_malformedCursor_invalidTimestamp_throws() {
|
||||||
|
String raw = "not-a-timestamp|agent-1";
|
||||||
|
String cursor = java.util.Base64.getUrlEncoder().withoutPadding()
|
||||||
|
.encodeToString(raw.getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
org.junit.jupiter.api.Assertions.assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> repo.queryPage(null, null, null, null, null, cursor, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queryPage_malformedCursor_emptyInstanceId_throws() {
|
||||||
|
String raw = "2026-04-01T10:00:00Z|";
|
||||||
|
String cursor = java.util.Base64.getUrlEncoder().withoutPadding()
|
||||||
|
.encodeToString(raw.getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
org.junit.jupiter.api.Assertions.assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> repo.queryPage(null, null, null, null, null, cursor, 10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user