From 9fd9b50842080550b93874a8dd2d61afeea49e2a Mon Sep 17 00:00:00 2001 From: nav Date: Thu, 9 Apr 2026 03:02:36 +0000 Subject: [PATCH] test: cover first-run pair bootstrap --- tests/runtime-flow.test.ts | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/runtime-flow.test.ts b/tests/runtime-flow.test.ts index 1c83e07..0e6324d 100644 --- a/tests/runtime-flow.test.ts +++ b/tests/runtime-flow.test.ts @@ -121,6 +121,53 @@ describe("Yonexus.Client runtime flow", () => { }); }); + it("SR-04: first run without credentials enters pair flow and does not require manual state bootstrap", async () => { + const storeState = createMockStateStore({ + identifier: "client-a", + updatedAt: 1_710_000_000 + }); + 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: () => 1_710_000_000 + }); + + 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: false, + hasKeyPair: true + }); + + await runtime.handleMessage( + encodeBuiltin( + buildHelloAck( + { + identifier: "client-a", + nextAction: "pair_required" + }, + { requestId: "req-first-run", timestamp: 1_710_000_000 } + ) + ) + ); + + expect(runtime.state.phase).toBe("pair_required"); + expect(runtime.state.clientState.secret).toBeUndefined(); + expect(runtime.state.clientState.privateKey).toBeTypeOf("string"); + expect(runtime.state.clientState.publicKey).toBeTypeOf("string"); + }); + it("handles pair request, submits code, stores secret, and authenticates", async () => { let now = 1_710_000_000; const storeState = createMockStateStore();