From ff5354ee4fbdacdc39b207e4bec3bb1576cfa81d Mon Sep 17 00:00:00 2001 From: "clawsweeper[bot]" <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 00:26:21 +0000 Subject: [PATCH] fix(twitch): export clearRegistryForTest for cross-test isolation (#83887) (#84309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: - The PR adds an async test-only Twitch client-manager registry reset helper, a focused registry isolation test, and an Unreleased changelog entry. - Reproducibility: yes. Source inspection shows getOrCreateClientManager() returns the cached module-level manager for the same account id, and the repo’s Vitest configuration is explicitly non-isolated. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(twitch): export clearRegistryForTest for cross-test isolation (#8… Validation: - ClawSweeper review passed for head 38c3fadc91636aeaff0e37397608512d8d713703. - Required merge gates passed before the squash merge. Prepared head SHA: 38c3fadc91636aeaff0e37397608512d8d713703 Review: https://github.com/openclaw/openclaw/pull/84309#issuecomment-4491930986 Co-authored-by: HCL Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com> --- CHANGELOG.md | 1 + .../src/client-manager-registry.test.ts | 36 +++++++++++++++++++ .../twitch/src/client-manager-registry.ts | 22 ++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 extensions/twitch/src/client-manager-registry.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b6dcba831..a0ec1d773e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai - CLI/message: include a stable top-level `messageId` in `openclaw message --json` output when channel sends return one. (#84191) Thanks @100menotu001. - Plugins/hooks: apply a default 30-second timeout to `before_compaction` and `after_compaction` hooks so a hung plugin handler no longer blocks compaction completion. (#84153) - Discord: preserve disabled presentation buttons when adapting and rendering Discord message controls. (#84188) Thanks @100menotu001. +- Twitch: add a test-only client-manager registry reset helper so non-isolated Twitch tests can clear cached managers between cases. Fixes #83887. (#84244) Thanks @hclsys. - Plugins/perf: thread explicit plugin discovery results through `loadBundledCapabilityRuntimeRegistry`, `resolveBundledPluginSources`, and `listChannelCatalogEntries` so callers that already hold a discovery result skip redundant filesystem walks. Thanks @SebTardif. - harden update restart script creation [AI]. (#84088) Thanks @pgondhi987. - Docker: keep the bundled Codex plugin in official release image keep lists so the default OpenAI agent harness remains available after Docker pruning. Fixes #83613. (#83626) Thanks @YuanHanzhong. diff --git a/extensions/twitch/src/client-manager-registry.test.ts b/extensions/twitch/src/client-manager-registry.test.ts new file mode 100644 index 00000000000..27702f7b458 --- /dev/null +++ b/extensions/twitch/src/client-manager-registry.test.ts @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + clearRegistryForTest, + getClientManager, + getOrCreateClientManager, +} from "./client-manager-registry.js"; +import type { ChannelLogSink } from "./types.js"; + +function makeLogger(): ChannelLogSink { + return { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }; +} + +describe("client manager registry", () => { + afterEach(async () => { + await clearRegistryForTest(); + }); + + it("clears cached managers for hot module test isolation", async () => { + const firstManager = getOrCreateClientManager("default", makeLogger()); + const disconnectAll = vi.spyOn(firstManager, "disconnectAll"); + + expect(getClientManager("default")).toBe(firstManager); + expect(getOrCreateClientManager("default", makeLogger())).toBe(firstManager); + + await clearRegistryForTest(); + + expect(disconnectAll).toHaveBeenCalledOnce(); + expect(getClientManager("default")).toBeUndefined(); + expect(getOrCreateClientManager("default", makeLogger())).not.toBe(firstManager); + }); +}); diff --git a/extensions/twitch/src/client-manager-registry.ts b/extensions/twitch/src/client-manager-registry.ts index 5338cd85ee0..77a77cdf669 100644 --- a/extensions/twitch/src/client-manager-registry.ts +++ b/extensions/twitch/src/client-manager-registry.ts @@ -85,3 +85,25 @@ export async function removeClientManager(accountId: string): Promise { registry.delete(accountId); entry.logger.info(`Unregistered client manager for account: ${accountId}`); } + +/** + * Test-only: clear the module-level registry of all client manager entries. + * + * Mirrors the `clearForTest` escape hatch on `TwitchClientManager`. Without + * this, the module-level `registry` Map survives across tests when vitest + * is run with `--isolate=false` (or any harness that does not tear the + * module graph down between cases), and a stale entry from one test will + * shadow `getOrCreateClientManager` calls in subsequent tests, silently + * handing back another test's mocked logger/manager. See #83887. + * + * Production code MUST NOT call this. It disconnects cached managers before + * clearing the registry so tests do not leave handlers or clients behind. + */ +export async function clearRegistryForTest(): Promise { + const entries = [...registry.values()]; + try { + await Promise.all(entries.map((entry) => entry.manager.disconnectAll())); + } finally { + registry.clear(); + } +}