@@ -364,4 +364,44 @@ describe("createLazyChannelApprovalNativeRuntimeAdapter", () => {
|
364 | 364 | expect(onDelivered).toHaveBeenCalledWith({ request: { id: "exec:1" } }); |
365 | 365 | expect(load).toHaveBeenCalledTimes(1); |
366 | 366 | }); |
| 367 | + |
| 368 | +it("unbinds in-flight wrapped entry when stop() fires between deliverPending and bindPending", async () => { |
| 369 | +const deliverGate = { resolve: () => {}, promise: Promise.resolve() }; |
| 370 | +const deliverPromise = new Promise<void>((resolve) => { |
| 371 | +deliverGate.resolve = resolve; |
| 372 | +}); |
| 373 | +deliverGate.promise = deliverPromise; |
| 374 | +const deliverPending = vi.fn(async () => { |
| 375 | +await deliverPromise; |
| 376 | +return { messageId: "in-flight" }; |
| 377 | +}); |
| 378 | +const bindPending = vi.fn().mockResolvedValue({ bindingId: "bound-in-flight" }); |
| 379 | +const unbindPending = vi.fn(); |
| 380 | + |
| 381 | +const runtime = await createTestApprovalHandler( |
| 382 | +makeNativeApprovalCapability({ |
| 383 | + deliverPending, |
| 384 | + bindPending, |
| 385 | + unbindPending, |
| 386 | +}), |
| 387 | +); |
| 388 | +const approvalRuntime = expectApprovalRuntime(runtime); |
| 389 | +const request = makeExecApprovalRequest("exec:in-flight"); |
| 390 | + |
| 391 | +const inflight = approvalRuntime.handleRequested(request); |
| 392 | +await new Promise((r) => setTimeout(r, 0)); |
| 393 | + |
| 394 | +// stop() while deliverPending is parked — onStopped flips the closure flag. |
| 395 | +await approvalRuntime.stop(); |
| 396 | +deliverGate.resolve(); |
| 397 | +await inflight; |
| 398 | + |
| 399 | +expect(unbindPending).toHaveBeenCalledTimes(1); |
| 400 | +const unbind = firstCallArg(unbindPending) as |
| 401 | +| { entry?: unknown; binding?: unknown; request?: unknown } |
| 402 | +| undefined; |
| 403 | +expect(unbind?.entry).toEqual({ messageId: "in-flight" }); |
| 404 | +expect(unbind?.request).toBe(request); |
| 405 | +expect(bindPending).not.toHaveBeenCalled(); |
| 406 | +}); |
367 | 407 | }); |