|
11 | 11 | import static org.junit.jupiter.api.Assertions.assertTrue; |
12 | 12 |
|
13 | 13 | import java.lang.reflect.Field; |
| 14 | +import java.lang.reflect.Method; |
14 | 15 | import java.nio.file.Paths; |
15 | 16 | import java.util.ArrayList; |
16 | 17 | import java.util.List; |
@@ -178,6 +179,52 @@ void shouldFailAndCleanPendingPromptWhenClientCloses() throws Exception { |
178 | 179 | client.close(); |
179 | 180 | } |
180 | 181 |
|
| 182 | + |
| 183 | + @Test |
| 184 | + void shouldExposeLifecycleStateTransitions() throws Exception { |
| 185 | + KimiAcpClient client = new KimiAcpClient(config("normal")); |
| 186 | + assertEquals("NEW", lifecycleState(client)); |
| 187 | + |
| 188 | + client.connect(); |
| 189 | + assertEquals("READY", lifecycleState(client)); |
| 190 | + |
| 191 | + client.close(); |
| 192 | + assertEquals("CLOSED", lifecycleState(client)); |
| 193 | + } |
| 194 | + |
| 195 | + @Test |
| 196 | + void shouldMarkTransportFailedAndRejectNewRpcAfterMalformedFrame() throws Exception { |
| 197 | + try (KimiAcpClient client = new KimiAcpClient(config("malformed-prompt"))) { |
| 198 | + client.connect(); |
| 199 | + String sessionId = client.newSession("/tmp"); |
| 200 | + |
| 201 | + CompletableFuture<KimiAcpTurnResult> future = |
| 202 | + client.promptAsync(sessionId, "bad-frame-state", null); |
| 203 | + assertThrows(ExecutionException.class, () -> future.get(2, TimeUnit.SECONDS)); |
| 204 | + |
| 205 | + assertEquals("FAILED", lifecycleState(client)); |
| 206 | + assertThrows(KimiException.class, client::listSessions, |
| 207 | + "a failed transport must reject new RPCs before registration"); |
| 208 | + assertEquals(0, privateMapSize(client, "pendingRpcs")); |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + @Test |
| 213 | + void shouldReturnToNewAfterRecoverableConnectFailure() throws Exception { |
| 214 | + KimiAcpConfig bad = config("normal"); |
| 215 | + bad.setLocalExecutable("/nonexistent/kimi"); |
| 216 | + try (KimiAcpClient client = new KimiAcpClient(bad)) { |
| 217 | + assertThrows(KimiException.class, client::connect); |
| 218 | + assertEquals("NEW", lifecycleState(client), |
| 219 | + "spawn/initialize failure remains retryable on the same client"); |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + private static String lifecycleState(KimiAcpClient client) throws Exception { |
| 224 | + Method method = KimiAcpClient.class.getMethod("getState"); |
| 225 | + return String.valueOf(method.invoke(client)); |
| 226 | + } |
| 227 | + |
181 | 228 | @SuppressWarnings("unchecked") |
182 | 229 | private static int privateMapSize(KimiAcpClient client, String fieldName) throws Exception { |
183 | 230 | Field field = KimiAcpClient.class.getDeclaredField(fieldName); |
|
0 commit comments