diff --git a/tests/runtime-flow.test.ts b/tests/runtime-flow.test.ts index 196487b..1c83e07 100644 --- a/tests/runtime-flow.test.ts +++ b/tests/runtime-flow.test.ts @@ -279,6 +279,63 @@ describe("Yonexus.Client runtime flow", () => { expect(runtime.state.lastPairingFailure).toBe("re_pair_required"); }); + it("SR-03: restarts with stored credentials and resumes at auth flow without re-pairing", async () => { + const now = 1_710_000_000; + const { generateKeyPair } = await import("../plugin/crypto/keypair.js"); + const keyPair = await generateKeyPair(); + const storeState = createMockStateStore({ + identifier: "client-a", + publicKey: keyPair.publicKey, + privateKey: keyPair.privateKey, + secret: "stored-secret", + pairedAt: now - 20, + authenticatedAt: now - 10, + updatedAt: now - 10 + }); + const transportState = createMockTransport(); + const runtime = createYonexusClientRuntime({ + config: { + mainHost: "ws://localhost:8787", + identifier: "client-a", + notifyBotToken: "stub-token", + adminUserId: "admin-user" + }, + transport: transportState.transport, + stateStore: storeState.store, + now: () => now + }); + + await runtime.start(); + runtime.handleTransportStateChange("connected"); + + const hello = decodeBuiltin(transportState.sent[0]); + expect(hello.type).toBe("hello"); + expect(hello.payload).toMatchObject({ + identifier: "client-a", + hasSecret: true, + hasKeyPair: true, + publicKey: keyPair.publicKey + }); + + await runtime.handleMessage( + encodeBuiltin( + buildHelloAck( + { + identifier: "client-a", + nextAction: "auth_required" + }, + { requestId: "req-restart-hello", timestamp: now } + ) + ) + ); + + expect(runtime.state.phase).toBe("auth_required"); + + const authRequest = decodeBuiltin(transportState.sent.at(-1)!); + expect(authRequest.type).toBe("auth_request"); + expect(authRequest.payload).toMatchObject({ identifier: "client-a" }); + }); + it("sends heartbeat only when authenticated and connected", async () => { const storeState = createMockStateStore({ identifier: "client-a",