mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(mattermost): anchor slash state on globalThis (#68113)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Mattermost tests cover slash state plugin behavior.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { ResolvedMattermostAccount } from "./accounts.js";
|
||||
import type { MattermostRegisteredCommand } from "./slash-commands.js";
|
||||
@@ -48,6 +48,49 @@ const slashApi = {
|
||||
runtime: RuntimeEnv;
|
||||
};
|
||||
|
||||
const ACCOUNT_STATES_KEY = Symbol.for("openclaw.mattermost.slash-account-states");
|
||||
|
||||
describe("slash-state global singleton", () => {
|
||||
afterEach(() => {
|
||||
deactivateSlashCommands();
|
||||
});
|
||||
|
||||
it("anchors accountStates on globalThis", () => {
|
||||
deactivateSlashCommands();
|
||||
activateSlashCommands({
|
||||
account: createResolvedMattermostAccount("a1"),
|
||||
commandTokens: ["tok-a"],
|
||||
registeredCommands: [],
|
||||
api: slashApi,
|
||||
});
|
||||
|
||||
const globalStore = globalThis as Record<PropertyKey, unknown>;
|
||||
const map = globalStore[ACCOUNT_STATES_KEY];
|
||||
expect(map).toBeInstanceOf(Map);
|
||||
expect((map as Map<string, unknown>).has("a1")).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves slash state across module reloads", async () => {
|
||||
deactivateSlashCommands();
|
||||
activateSlashCommands({
|
||||
account: createResolvedMattermostAccount("a1"),
|
||||
commandTokens: ["tok-reload"],
|
||||
registeredCommands: [],
|
||||
api: slashApi,
|
||||
});
|
||||
|
||||
vi.resetModules();
|
||||
const reloaded = await import("./slash-state.js");
|
||||
const match = reloaded.resolveSlashHandlerForToken("tok-reload");
|
||||
|
||||
expect(match.kind).toBe("single");
|
||||
if (match.kind !== "single") {
|
||||
throw new Error("expected single match after module reload");
|
||||
}
|
||||
expect(match.accountIds).toEqual(["a1"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("slash-state token routing", () => {
|
||||
it("returns single match when token belongs to one account", () => {
|
||||
deactivateSlashCommands();
|
||||
|
||||
@@ -62,8 +62,29 @@ type SlashCommandAccountState = {
|
||||
triggerMap: Map<string, string>;
|
||||
};
|
||||
|
||||
/** Map from accountId → per-account slash command state. */
|
||||
const accountStates = new Map<string, SlashCommandAccountState>();
|
||||
/**
|
||||
* Map from accountId → per-account slash command state.
|
||||
*
|
||||
* Anchored to globalThis so that jiti-loaded (route registration) and
|
||||
* native-ESM-loaded (monitor/activation) module instances share the
|
||||
* same Map. Without this, each module loader creates its own copy of
|
||||
* the module-level variable and the HTTP handler never sees the tokens
|
||||
* populated by the monitor.
|
||||
*/
|
||||
const ACCOUNT_STATES_KEY = Symbol.for("openclaw.mattermost.slash-account-states");
|
||||
|
||||
function getSlashAccountStates(): Map<string, SlashCommandAccountState> {
|
||||
const globalStore = globalThis as Record<PropertyKey, unknown>;
|
||||
const existing = globalStore[ACCOUNT_STATES_KEY];
|
||||
if (existing instanceof Map) {
|
||||
return existing as Map<string, SlashCommandAccountState>;
|
||||
}
|
||||
const accountStates = new Map<string, SlashCommandAccountState>();
|
||||
globalStore[ACCOUNT_STATES_KEY] = accountStates;
|
||||
return accountStates;
|
||||
}
|
||||
|
||||
const accountStates = getSlashAccountStates();
|
||||
|
||||
export function resolveSlashHandlerForToken(token: string): SlashHandlerMatch {
|
||||
const matches: Array<{
|
||||
|
||||
Reference in New Issue
Block a user