惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
The GitHub Blog
The GitHub Blog
A
About on SuperTechFans
P
Proofpoint News Feed
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
C
Check Point Blog
Microsoft Azure Blog
Microsoft Azure Blog
aimingoo的专栏
aimingoo的专栏
月光博客
月光博客
美团技术团队
D
Docker
博客园 - Franky
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
罗磊的独立博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(gateway): resolve plugin-registered gateway methods t...
Pick-cat · 2026-06-24 · via Recent Commits to openclaw:main
1+

/**

2+

* Regression tests for plugin-registered gateway RPC dispatch (#94127).

3+

*/

4+

import { afterEach, describe, expect, it, vi } from "vitest";

5+

import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";

6+

import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js";

7+

import {

8+

createGatewayMethodRegistry,

9+

createPluginGatewayMethodDescriptor,

10+

} from "./methods/registry.js";

11+

import { WRITE_SCOPE } from "./operator-scopes.js";

12+

import { handleGatewayRequest } from "./server-methods.js";

13+

import type { GatewayRequestHandler } from "./server-methods/types.js";

14+15+

describe("handleGatewayRequest plugin gateway dispatch", () => {

16+

afterEach(() => {

17+

resetPluginRuntimeStateForTest();

18+

});

19+20+

it("dispatches plugin methods registered after the startup method registry snapshot", async () => {

21+

const handler = vi.fn<GatewayRequestHandler>(({ respond }) => {

22+

respond(true, { ok: true, ts: 42 });

23+

});

24+

const activeRegistry = createEmptyPluginRegistry();

25+

activeRegistry.gatewayHandlers["demo.ping"] = handler;

26+

activeRegistry.gatewayMethodDescriptors.push(

27+

createPluginGatewayMethodDescriptor({

28+

pluginId: "demo",

29+

name: "demo.ping",

30+

handler,

31+

scope: WRITE_SCOPE,

32+

}),

33+

);

34+

setActivePluginRegistry(activeRegistry);

35+36+

const staleStartupRegistry = createGatewayMethodRegistry([]);

37+

const respond = vi.fn();

38+

await handleGatewayRequest({

39+

req: {

40+

type: "req",

41+

id: "proof-94127",

42+

method: "demo.ping",

43+

params: { hello: "world" },

44+

},

45+

respond,

46+

client: {

47+

connId: "conn-proof",

48+

connect: {

49+

role: "operator",

50+

scopes: [WRITE_SCOPE],

51+

client: {

52+

id: "cli",

53+

version: "test",

54+

platform: "linux",

55+

mode: "cli",

56+

},

57+

minProtocol: 1,

58+

maxProtocol: 1,

59+

},

60+

},

61+

isWebchatConnect: () => false,

62+

context: {

63+

logGateway: { warn: vi.fn() },

64+

} as unknown as Parameters<typeof handleGatewayRequest>[0]["context"],

65+

methodRegistry: staleStartupRegistry,

66+

});

67+68+

expect(handler).toHaveBeenCalledTimes(1);

69+

expect(respond).toHaveBeenCalledWith(true, { ok: true, ts: 42 });

70+

});

71+72+

it("dispatches a method owned by the caller-attached registry even when global state lacks it (#94343)", async () => {

73+

const handler = vi.fn<GatewayRequestHandler>(({ respond }) => {

74+

respond(true, { ok: true, source: "attached" });

75+

});

76+

// Active plugin registry does NOT carry the method; only the caller-attached

77+

// snapshot owns it, so dispatch must prefer the attached registry.

78+

setActivePluginRegistry(createEmptyPluginRegistry());

79+

const attachedRegistry = createGatewayMethodRegistry([

80+

createPluginGatewayMethodDescriptor({

81+

pluginId: "demo",

82+

name: "demo.attached",

83+

handler,

84+

scope: WRITE_SCOPE,

85+

}),

86+

]);

87+

const respond = vi.fn();

88+

await handleGatewayRequest({

89+

req: { type: "req", id: "proof-94343", method: "demo.attached", params: {} },

90+

respond,

91+

client: {

92+

connId: "conn-proof",

93+

connect: {

94+

role: "operator",

95+

scopes: [WRITE_SCOPE],

96+

client: { id: "cli", version: "test", platform: "linux", mode: "cli" },

97+

minProtocol: 1,

98+

maxProtocol: 1,

99+

},

100+

},

101+

isWebchatConnect: () => false,

102+

context: {

103+

logGateway: { warn: vi.fn() },

104+

} as unknown as Parameters<typeof handleGatewayRequest>[0]["context"],

105+

methodRegistry: attachedRegistry,

106+

});

107+108+

expect(handler).toHaveBeenCalledTimes(1);

109+

expect(respond).toHaveBeenCalledWith(true, { ok: true, source: "attached" });

110+

});

111+112+

it("fails closed when neither the attached snapshot nor the live registry owns the method", async () => {

113+

const handler = vi.fn<GatewayRequestHandler>();

114+

setActivePluginRegistry(createEmptyPluginRegistry());

115+

const respond = vi.fn();

116+

await handleGatewayRequest({

117+

req: { type: "req", id: "proof-unknown", method: "demo.does-not-exist", params: {} },

118+

respond,

119+

client: {

120+

connId: "conn-proof",

121+

connect: {

122+

role: "operator",

123+

scopes: [WRITE_SCOPE],

124+

client: { id: "cli", version: "test", platform: "linux", mode: "cli" },

125+

minProtocol: 1,

126+

maxProtocol: 1,

127+

},

128+

},

129+

isWebchatConnect: () => false,

130+

context: {

131+

logGateway: { warn: vi.fn() },

132+

} as unknown as Parameters<typeof handleGatewayRequest>[0]["context"],

133+

methodRegistry: createGatewayMethodRegistry([]),

134+

});

135+136+

expect(handler).not.toHaveBeenCalled();

137+

const [ok] = respond.mock.calls.at(-1) ?? [];

138+

expect(ok).toBe(false);

139+

});

140+

});