|
1 | 1 | // Runtime channel tests cover channel plugin runtime send, reply, and capability behavior. |
| 2 | +import { getEventListeners } from "node:events"; |
2 | 3 | import { describe, expect, it, vi } from "vitest"; |
3 | 4 | import { createRuntimeChannel } from "./runtime-channel.js"; |
4 | 5 |
|
@@ -89,6 +90,60 @@ describe("runtimeContexts", () => { |
89 | 90 | lease.dispose(); |
90 | 91 | }); |
91 | 92 |
|
| 93 | + it("removes its abort listener when the lease is disposed", () => { |
| 94 | + const channel = createRuntimeChannel(); |
| 95 | + const controller = new AbortController(); |
| 96 | + const initialListenerCount = getEventListeners(controller.signal, "abort").length; |
| 97 | + const lease = channel.runtimeContexts.register({ |
| 98 | + channelId: "telegram", |
| 99 | + accountId: "default", |
| 100 | + capability: "approval.native", |
| 101 | + context: { token: "abc" }, |
| 102 | + abortSignal: controller.signal, |
| 103 | + }); |
| 104 | + |
| 105 | + expect(getEventListeners(controller.signal, "abort")).toHaveLength(initialListenerCount + 1); |
| 106 | + |
| 107 | + lease.dispose(); |
| 108 | + |
| 109 | + expect(getEventListeners(controller.signal, "abort")).toHaveLength(initialListenerCount); |
| 110 | + }); |
| 111 | + |
| 112 | + it("removes the stale lease abort listener after a replacement registration", () => { |
| 113 | + const channel = createRuntimeChannel(); |
| 114 | + const controller = new AbortController(); |
| 115 | + const initialListenerCount = getEventListeners(controller.signal, "abort").length; |
| 116 | + const staleLease = channel.runtimeContexts.register({ |
| 117 | + channelId: "whatsapp", |
| 118 | + accountId: "default", |
| 119 | + capability: "connection.controller", |
| 120 | + context: { token: "stale" }, |
| 121 | + abortSignal: controller.signal, |
| 122 | + }); |
| 123 | + channel.runtimeContexts.register({ |
| 124 | + channelId: "whatsapp", |
| 125 | + accountId: "default", |
| 126 | + capability: "connection.controller", |
| 127 | + context: { token: "replacement" }, |
| 128 | + abortSignal: controller.signal, |
| 129 | + }); |
| 130 | + |
| 131 | + expect(getEventListeners(controller.signal, "abort")).toHaveLength(initialListenerCount + 2); |
| 132 | + |
| 133 | + // Channel plugins dispose the previous lease after registering its replacement, |
| 134 | + // so the stale token check must not skip listener cleanup. |
| 135 | + staleLease.dispose(); |
| 136 | + |
| 137 | + expect(getEventListeners(controller.signal, "abort")).toHaveLength(initialListenerCount + 1); |
| 138 | + expect( |
| 139 | + channel.runtimeContexts.get({ |
| 140 | + channelId: "whatsapp", |
| 141 | + accountId: "default", |
| 142 | + capability: "connection.controller", |
| 143 | + }), |
| 144 | + ).toEqual({ token: "replacement" }); |
| 145 | + }); |
| 146 | + |
92 | 147 | it("does not register contexts when the abort signal is already aborted", () => { |
93 | 148 | const channel = createRuntimeChannel(); |
94 | 149 | const onEvent = vi.fn(); |
|
0 commit comments