@f2a/network 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -1
- package/.github/workflows/ci.yml +0 -113
- package/.github/workflows/publish.yml +0 -60
- package/MONOREPO.md +0 -58
- package/SKILL.md +0 -137
- package/dist/adapters/openclaw.d.ts +0 -103
- package/dist/adapters/openclaw.d.ts.map +0 -1
- package/dist/adapters/openclaw.js +0 -297
- package/dist/adapters/openclaw.js.map +0 -1
- package/dist/core/connection-manager.d.ts +0 -80
- package/dist/core/connection-manager.d.ts.map +0 -1
- package/dist/core/connection-manager.js +0 -235
- package/dist/core/connection-manager.js.map +0 -1
- package/dist/core/connection-manager.test.d.ts +0 -2
- package/dist/core/connection-manager.test.d.ts.map +0 -1
- package/dist/core/connection-manager.test.js +0 -52
- package/dist/core/connection-manager.test.js.map +0 -1
- package/dist/core/identity.d.ts +0 -47
- package/dist/core/identity.d.ts.map +0 -1
- package/dist/core/identity.js +0 -130
- package/dist/core/identity.js.map +0 -1
- package/dist/core/identity.test.d.ts +0 -2
- package/dist/core/identity.test.d.ts.map +0 -1
- package/dist/core/identity.test.js +0 -43
- package/dist/core/identity.test.js.map +0 -1
- package/dist/core/serverless.d.ts +0 -155
- package/dist/core/serverless.d.ts.map +0 -1
- package/dist/core/serverless.js +0 -615
- package/dist/core/serverless.js.map +0 -1
- package/dist/daemon/webhook.test.d.ts +0 -2
- package/dist/daemon/webhook.test.d.ts.map +0 -1
- package/dist/daemon/webhook.test.js +0 -24
- package/dist/daemon/webhook.test.js.map +0 -1
- package/dist/protocol/messages.d.ts +0 -739
- package/dist/protocol/messages.d.ts.map +0 -1
- package/dist/protocol/messages.js +0 -188
- package/dist/protocol/messages.js.map +0 -1
- package/dist/protocol/messages.test.d.ts +0 -2
- package/dist/protocol/messages.test.d.ts.map +0 -1
- package/dist/protocol/messages.test.js +0 -55
- package/dist/protocol/messages.test.js.map +0 -1
- package/docs/F2A-PROTOCOL.md +0 -61
- package/docs/MOBILE_BOOTSTRAP_DESIGN.md +0 -126
- package/docs/a2a-lessons.md +0 -316
- package/docs/middleware-guide.md +0 -448
- package/docs/readme-update-checklist.md +0 -90
- package/docs/reputation-guide.md +0 -396
- package/docs/rfcs/001-reputation-system.md +0 -712
- package/docs/security-design.md +0 -247
- package/install.sh +0 -231
- package/packages/openclaw-adapter/README.md +0 -510
- package/packages/openclaw-adapter/openclaw.plugin.json +0 -106
- package/packages/openclaw-adapter/package.json +0 -40
- package/packages/openclaw-adapter/src/announcement-queue.test.ts +0 -449
- package/packages/openclaw-adapter/src/announcement-queue.ts +0 -403
- package/packages/openclaw-adapter/src/capability-detector.test.ts +0 -99
- package/packages/openclaw-adapter/src/capability-detector.ts +0 -183
- package/packages/openclaw-adapter/src/claim-handlers.test.ts +0 -974
- package/packages/openclaw-adapter/src/claim-handlers.ts +0 -482
- package/packages/openclaw-adapter/src/connector.business.test.ts +0 -583
- package/packages/openclaw-adapter/src/connector.ts +0 -795
- package/packages/openclaw-adapter/src/index.test.ts +0 -82
- package/packages/openclaw-adapter/src/index.ts +0 -18
- package/packages/openclaw-adapter/src/integration.e2e.test.ts +0 -829
- package/packages/openclaw-adapter/src/logger.ts +0 -51
- package/packages/openclaw-adapter/src/network-client.test.ts +0 -266
- package/packages/openclaw-adapter/src/network-client.ts +0 -251
- package/packages/openclaw-adapter/src/network-recovery.test.ts +0 -465
- package/packages/openclaw-adapter/src/node-manager.test.ts +0 -136
- package/packages/openclaw-adapter/src/node-manager.ts +0 -429
- package/packages/openclaw-adapter/src/plugin.test.ts +0 -439
- package/packages/openclaw-adapter/src/plugin.ts +0 -104
- package/packages/openclaw-adapter/src/reputation.test.ts +0 -221
- package/packages/openclaw-adapter/src/reputation.ts +0 -368
- package/packages/openclaw-adapter/src/task-guard.test.ts +0 -502
- package/packages/openclaw-adapter/src/task-guard.ts +0 -860
- package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +0 -462
- package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +0 -284
- package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +0 -408
- package/packages/openclaw-adapter/src/task-queue.ts +0 -668
- package/packages/openclaw-adapter/src/tool-handlers.test.ts +0 -906
- package/packages/openclaw-adapter/src/tool-handlers.ts +0 -574
- package/packages/openclaw-adapter/src/types.ts +0 -361
- package/packages/openclaw-adapter/src/webhook-pusher.test.ts +0 -188
- package/packages/openclaw-adapter/src/webhook-pusher.ts +0 -220
- package/packages/openclaw-adapter/src/webhook-server.test.ts +0 -580
- package/packages/openclaw-adapter/src/webhook-server.ts +0 -202
- package/packages/openclaw-adapter/tsconfig.json +0 -20
- package/src/cli/commands.test.ts +0 -157
- package/src/cli/commands.ts +0 -129
- package/src/cli/index.test.ts +0 -77
- package/src/cli/index.ts +0 -234
- package/src/core/autonomous-economy.test.ts +0 -291
- package/src/core/autonomous-economy.ts +0 -428
- package/src/core/e2ee-crypto.test.ts +0 -125
- package/src/core/e2ee-crypto.ts +0 -246
- package/src/core/f2a.test.ts +0 -269
- package/src/core/f2a.ts +0 -618
- package/src/core/p2p-network.test.ts +0 -199
- package/src/core/p2p-network.ts +0 -1432
- package/src/core/reputation-security.test.ts +0 -403
- package/src/core/reputation-security.ts +0 -562
- package/src/core/reputation.test.ts +0 -260
- package/src/core/reputation.ts +0 -576
- package/src/core/review-committee.test.ts +0 -380
- package/src/core/review-committee.ts +0 -401
- package/src/core/token-manager.test.ts +0 -133
- package/src/core/token-manager.ts +0 -140
- package/src/daemon/control-server.test.ts +0 -216
- package/src/daemon/control-server.ts +0 -292
- package/src/daemon/index.test.ts +0 -85
- package/src/daemon/index.ts +0 -89
- package/src/daemon/main.ts +0 -44
- package/src/daemon/start.ts +0 -29
- package/src/daemon/webhook.test.ts +0 -68
- package/src/daemon/webhook.ts +0 -105
- package/src/index.test.ts +0 -436
- package/src/index.ts +0 -72
- package/src/types/index.test.ts +0 -87
- package/src/types/index.ts +0 -341
- package/src/types/result.ts +0 -68
- package/src/utils/benchmark.ts +0 -237
- package/src/utils/logger.ts +0 -331
- package/src/utils/middleware.ts +0 -229
- package/src/utils/rate-limiter.ts +0 -207
- package/src/utils/signature.ts +0 -136
- package/src/utils/validation.ts +0 -186
- package/tests/docker/Dockerfile.node +0 -23
- package/tests/docker/Dockerfile.runner +0 -18
- package/tests/docker/docker-compose.test.yml +0 -73
- package/tests/integration/message-passing.test.ts +0 -109
- package/tests/integration/multi-node.test.ts +0 -92
- package/tests/integration/p2p-connection.test.ts +0 -83
- package/tests/integration/test-config.ts +0 -32
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -26
|
@@ -1,580 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { WebhookServer, WebhookHandler } from './webhook-server';
|
|
3
|
-
import { AgentCapability } from './types';
|
|
4
|
-
import { createServer, IncomingMessage, ServerResponse } from 'http';
|
|
5
|
-
|
|
6
|
-
// 使用真实的 http 模块进行集成测试
|
|
7
|
-
vi.unmock('http');
|
|
8
|
-
|
|
9
|
-
describe('WebhookServer', () => {
|
|
10
|
-
let server: WebhookServer;
|
|
11
|
-
let mockHandler: WebhookHandler;
|
|
12
|
-
let testPort: number;
|
|
13
|
-
|
|
14
|
-
const mockCapabilities: AgentCapability[] = [
|
|
15
|
-
{
|
|
16
|
-
name: 'file-operation',
|
|
17
|
-
description: 'File operations',
|
|
18
|
-
tools: ['read', 'write'],
|
|
19
|
-
},
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
// 获取可用端口
|
|
23
|
-
async function getAvailablePort(): Promise<number> {
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
const tempServer = createServer();
|
|
26
|
-
tempServer.listen(0, () => {
|
|
27
|
-
const address = tempServer.address();
|
|
28
|
-
if (address && typeof address === 'object') {
|
|
29
|
-
const port = address.port;
|
|
30
|
-
tempServer.close(() => resolve(port));
|
|
31
|
-
} else {
|
|
32
|
-
reject(new Error('Failed to get port'));
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
beforeEach(async () => {
|
|
39
|
-
vi.clearAllMocks();
|
|
40
|
-
testPort = await getAvailablePort();
|
|
41
|
-
mockHandler = {
|
|
42
|
-
onDiscover: vi.fn().mockResolvedValue({
|
|
43
|
-
capabilities: mockCapabilities,
|
|
44
|
-
reputation: 80,
|
|
45
|
-
}),
|
|
46
|
-
onDelegate: vi.fn().mockResolvedValue({
|
|
47
|
-
accepted: true,
|
|
48
|
-
taskId: 'task-123',
|
|
49
|
-
}),
|
|
50
|
-
onStatus: vi.fn().mockResolvedValue({
|
|
51
|
-
status: 'available',
|
|
52
|
-
load: 0.5,
|
|
53
|
-
}),
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
afterEach(async () => {
|
|
58
|
-
if (server) {
|
|
59
|
-
await server.stop();
|
|
60
|
-
// P1 修复:等待端口完全释放,避免 EADDRINUSE 错误
|
|
61
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// 辅助函数:发送 HTTP 请求
|
|
66
|
-
async function sendRequest(
|
|
67
|
-
options: {
|
|
68
|
-
method?: string;
|
|
69
|
-
path?: string;
|
|
70
|
-
headers?: Record<string, string>;
|
|
71
|
-
body?: unknown;
|
|
72
|
-
bodySize?: number; // 用于测试大请求体
|
|
73
|
-
} = {}
|
|
74
|
-
): Promise<{ status: number; body: unknown; headers: Record<string, string> }> {
|
|
75
|
-
return new Promise((resolve, reject) => {
|
|
76
|
-
const req = {
|
|
77
|
-
hostname: 'localhost',
|
|
78
|
-
port: testPort,
|
|
79
|
-
path: options.path || '/',
|
|
80
|
-
method: options.method || 'POST',
|
|
81
|
-
headers: {
|
|
82
|
-
'Content-Type': 'application/json',
|
|
83
|
-
...options.headers,
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const httpReq = require('http').request(req, (res: any) => {
|
|
88
|
-
let data = '';
|
|
89
|
-
const responseHeaders: Record<string, string> = {};
|
|
90
|
-
for (const [key, value] of Object.entries(res.headers)) {
|
|
91
|
-
if (typeof value === 'string') {
|
|
92
|
-
responseHeaders[key] = value;
|
|
93
|
-
} else if (Array.isArray(value)) {
|
|
94
|
-
responseHeaders[key] = value.join(', ');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
res.on('data', (chunk: Buffer) => (data += chunk));
|
|
98
|
-
res.on('end', () => {
|
|
99
|
-
try {
|
|
100
|
-
const body = data ? JSON.parse(data) : {};
|
|
101
|
-
resolve({ status: res.statusCode, body, headers: responseHeaders });
|
|
102
|
-
} catch {
|
|
103
|
-
resolve({ status: res.statusCode, body: data, headers: responseHeaders });
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
httpReq.on('error', reject);
|
|
109
|
-
|
|
110
|
-
if (options.bodySize) {
|
|
111
|
-
// 发送指定大小的请求体
|
|
112
|
-
httpReq.setHeader('Content-Length', options.bodySize);
|
|
113
|
-
httpReq.write('x'.repeat(options.bodySize));
|
|
114
|
-
} else if (options.body) {
|
|
115
|
-
httpReq.write(JSON.stringify(options.body));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
httpReq.end();
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
describe('start', () => {
|
|
123
|
-
it('should start server successfully', async () => {
|
|
124
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
125
|
-
await expect(server.start()).resolves.not.toThrow();
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('stop', () => {
|
|
130
|
-
it('should stop server gracefully', async () => {
|
|
131
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
132
|
-
await server.start();
|
|
133
|
-
await expect(server.stop()).resolves.not.toThrow();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should not throw when stopping unstarted server', async () => {
|
|
137
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
138
|
-
await expect(server.stop()).resolves.not.toThrow();
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('getUrl', () => {
|
|
143
|
-
it('should return correct URL', async () => {
|
|
144
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
145
|
-
expect(server.getUrl()).toBe(`http://localhost:${testPort}/webhook`);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('请求体大小限制测试', () => {
|
|
150
|
-
it('应该接受正常大小的请求体', async () => {
|
|
151
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
152
|
-
await server.start();
|
|
153
|
-
|
|
154
|
-
const response = await sendRequest({
|
|
155
|
-
body: {
|
|
156
|
-
type: 'status',
|
|
157
|
-
payload: {},
|
|
158
|
-
timestamp: Date.now(),
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
expect(response.status).toBe(200);
|
|
163
|
-
expect(response.body).toHaveProperty('status', 'available');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('应该拒绝超过大小限制的请求体', async () => {
|
|
167
|
-
// 设置最大请求体大小为 100 字节
|
|
168
|
-
server = new WebhookServer(testPort, mockHandler, { maxBodySize: 100 });
|
|
169
|
-
await server.start();
|
|
170
|
-
|
|
171
|
-
// 发送超过 100 字节的请求体
|
|
172
|
-
const largeBody = {
|
|
173
|
-
type: 'discover',
|
|
174
|
-
payload: {
|
|
175
|
-
query: { capability: 'x'.repeat(200) }, // 超过限制
|
|
176
|
-
requester: 'test-requester',
|
|
177
|
-
},
|
|
178
|
-
timestamp: Date.now(),
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// 当请求体过大时,服务器会销毁连接或返回 500
|
|
182
|
-
try {
|
|
183
|
-
const response = await sendRequest({ body: largeBody });
|
|
184
|
-
// 如果收到响应,应该是 500 错误
|
|
185
|
-
expect(response.status).toBe(500);
|
|
186
|
-
expect(response.body).toHaveProperty('error');
|
|
187
|
-
} catch (error: any) {
|
|
188
|
-
// 如果连接被销毁,这是预期的行为(防止 DoS 攻击)
|
|
189
|
-
expect(error.code).toBe('ECONNRESET');
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('应该使用默认 64KB 限制', async () => {
|
|
194
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
195
|
-
await server.start();
|
|
196
|
-
|
|
197
|
-
// 发送一个较大的但不超过 64KB 的请求
|
|
198
|
-
const largeButValidBody = {
|
|
199
|
-
type: 'discover',
|
|
200
|
-
payload: {
|
|
201
|
-
query: { capability: 'x'.repeat(10000) }, // 约 10KB
|
|
202
|
-
requester: 'test-requester',
|
|
203
|
-
},
|
|
204
|
-
timestamp: Date.now(),
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const response = await sendRequest({ body: largeButValidBody });
|
|
208
|
-
expect(response.status).toBe(200);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('应该在请求体过大时销毁连接', async () => {
|
|
212
|
-
server = new WebhookServer(testPort, mockHandler, { maxBodySize: 100 });
|
|
213
|
-
await server.start();
|
|
214
|
-
|
|
215
|
-
// 当请求体过大时,服务器会销毁连接,导致 socket hang up
|
|
216
|
-
// 这是正确的行为(防止 DoS 攻击),所以我们需要捕获这个错误
|
|
217
|
-
try {
|
|
218
|
-
await sendRequest({
|
|
219
|
-
bodySize: 1000, // 超过 100 字节限制
|
|
220
|
-
});
|
|
221
|
-
// 如果没有抛出错误,说明服务器发送了响应(这也是可接受的)
|
|
222
|
-
} catch (error: any) {
|
|
223
|
-
// 连接被销毁是预期的行为
|
|
224
|
-
expect(error.code).toBe('ECONNRESET');
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
describe('CORS 测试', () => {
|
|
230
|
-
it('应该为允许的来源设置 CORS 头', async () => {
|
|
231
|
-
server = new WebhookServer(testPort, mockHandler, {
|
|
232
|
-
allowedOrigins: ['http://localhost', 'http://example.com'],
|
|
233
|
-
});
|
|
234
|
-
await server.start();
|
|
235
|
-
|
|
236
|
-
const response = await sendRequest({
|
|
237
|
-
headers: { Origin: 'http://example.com' },
|
|
238
|
-
body: { type: 'status', payload: {}, timestamp: Date.now() },
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
expect(response.headers['access-control-allow-origin']).toBe('http://example.com');
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('应该拒绝不在白名单中的来源', async () => {
|
|
245
|
-
server = new WebhookServer(testPort, mockHandler, {
|
|
246
|
-
allowedOrigins: ['http://localhost', 'http://example.com'],
|
|
247
|
-
});
|
|
248
|
-
await server.start();
|
|
249
|
-
|
|
250
|
-
const response = await sendRequest({
|
|
251
|
-
headers: { Origin: 'http://malicious.com' },
|
|
252
|
-
body: { type: 'status', payload: {}, timestamp: Date.now() },
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// 应该使用默认的 localhost 而不是恶意来源
|
|
256
|
-
expect(response.headers['access-control-allow-origin']).toBe('http://localhost');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('应该处理 OPTIONS 预检请求', async () => {
|
|
260
|
-
server = new WebhookServer(testPort, mockHandler, {
|
|
261
|
-
allowedOrigins: ['http://localhost', 'http://example.com'],
|
|
262
|
-
});
|
|
263
|
-
await server.start();
|
|
264
|
-
|
|
265
|
-
const response = await sendRequest({
|
|
266
|
-
method: 'OPTIONS',
|
|
267
|
-
headers: { Origin: 'http://example.com' },
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
expect(response.status).toBe(200);
|
|
271
|
-
expect(response.headers['access-control-allow-origin']).toBe('http://example.com');
|
|
272
|
-
expect(response.headers['access-control-allow-methods']).toBe('POST, OPTIONS');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('应该设置正确的 CORS 方法头', async () => {
|
|
276
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
277
|
-
await server.start();
|
|
278
|
-
|
|
279
|
-
const response = await sendRequest({
|
|
280
|
-
body: { type: 'status', payload: {}, timestamp: Date.now() },
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
expect(response.headers['access-control-allow-methods']).toBe('POST, OPTIONS');
|
|
284
|
-
expect(response.headers['access-control-allow-headers']).toBe('Content-Type');
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('默认只允许 localhost', async () => {
|
|
288
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
289
|
-
await server.start();
|
|
290
|
-
|
|
291
|
-
const response = await sendRequest({
|
|
292
|
-
headers: { Origin: 'http://unknown-origin.com' },
|
|
293
|
-
body: { type: 'status', payload: {}, timestamp: Date.now() },
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
expect(response.headers['access-control-allow-origin']).toBe('http://localhost');
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
describe('错误处理测试', () => {
|
|
301
|
-
it('应该拒绝非 POST 请求', async () => {
|
|
302
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
303
|
-
await server.start();
|
|
304
|
-
|
|
305
|
-
const response = await sendRequest({
|
|
306
|
-
method: 'GET',
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
expect(response.status).toBe(405);
|
|
310
|
-
expect(response.body).toHaveProperty('error', 'Method not allowed');
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('应该拒绝非 POST 请求 (PUT)', async () => {
|
|
314
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
315
|
-
await server.start();
|
|
316
|
-
|
|
317
|
-
const response = await sendRequest({
|
|
318
|
-
method: 'PUT',
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
expect(response.status).toBe(405);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('应该拒绝非 POST 请求 (DELETE)', async () => {
|
|
325
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
326
|
-
await server.start();
|
|
327
|
-
|
|
328
|
-
const response = await sendRequest({
|
|
329
|
-
method: 'DELETE',
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
expect(response.status).toBe(405);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('应该返回 400 对于未知事件类型', async () => {
|
|
336
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
337
|
-
await server.start();
|
|
338
|
-
|
|
339
|
-
const response = await sendRequest({
|
|
340
|
-
body: {
|
|
341
|
-
type: 'unknown-event',
|
|
342
|
-
payload: {},
|
|
343
|
-
timestamp: Date.now(),
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
expect(response.status).toBe(400);
|
|
348
|
-
expect(response.body).toHaveProperty('error');
|
|
349
|
-
expect((response.body as { error: string }).error).toContain('Unknown event type');
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('应该返回 500 对于无效 JSON', async () => {
|
|
353
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
354
|
-
await server.start();
|
|
355
|
-
|
|
356
|
-
const response = await new Promise<{ status: number; body: unknown }>((resolve, reject) => {
|
|
357
|
-
const httpReq = require('http').request(
|
|
358
|
-
{
|
|
359
|
-
hostname: 'localhost',
|
|
360
|
-
port: testPort,
|
|
361
|
-
method: 'POST',
|
|
362
|
-
headers: { 'Content-Type': 'application/json' },
|
|
363
|
-
},
|
|
364
|
-
(res: any) => {
|
|
365
|
-
let data = '';
|
|
366
|
-
res.on('data', (chunk: Buffer) => (data += chunk));
|
|
367
|
-
res.on('end', () => {
|
|
368
|
-
try {
|
|
369
|
-
resolve({ status: res.statusCode, body: JSON.parse(data) });
|
|
370
|
-
} catch {
|
|
371
|
-
resolve({ status: res.statusCode, body: data });
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
);
|
|
376
|
-
httpReq.on('error', reject);
|
|
377
|
-
httpReq.write('not a valid json');
|
|
378
|
-
httpReq.end();
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
expect(response.status).toBe(500);
|
|
382
|
-
expect(response.body).toHaveProperty('error', 'Invalid JSON');
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('应该返回 500 当 handler 抛出异常', async () => {
|
|
386
|
-
const errorHandler: WebhookHandler = {
|
|
387
|
-
onDiscover: vi.fn().mockRejectedValue(new Error('Handler error')),
|
|
388
|
-
onDelegate: vi.fn().mockResolvedValue({ accepted: true, taskId: 'task-123' }),
|
|
389
|
-
onStatus: vi.fn().mockResolvedValue({ status: 'available' }),
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
server = new WebhookServer(testPort, errorHandler);
|
|
393
|
-
await server.start();
|
|
394
|
-
|
|
395
|
-
const response = await sendRequest({
|
|
396
|
-
body: {
|
|
397
|
-
type: 'discover',
|
|
398
|
-
payload: { query: {}, requester: 'test' },
|
|
399
|
-
timestamp: Date.now(),
|
|
400
|
-
},
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
expect(response.status).toBe(500);
|
|
404
|
-
expect(response.body).toHaveProperty('error', 'Handler error');
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it('应该处理 onDelegate handler 异常', async () => {
|
|
408
|
-
const errorHandler: WebhookHandler = {
|
|
409
|
-
onDiscover: vi.fn().mockResolvedValue({ capabilities: mockCapabilities }),
|
|
410
|
-
onDelegate: vi.fn().mockRejectedValue(new Error('Delegate failed')),
|
|
411
|
-
onStatus: vi.fn().mockResolvedValue({ status: 'available' }),
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
server = new WebhookServer(testPort, errorHandler);
|
|
415
|
-
await server.start();
|
|
416
|
-
|
|
417
|
-
const response = await sendRequest({
|
|
418
|
-
body: {
|
|
419
|
-
type: 'delegate',
|
|
420
|
-
payload: {
|
|
421
|
-
taskId: 'task-1',
|
|
422
|
-
taskType: 'test',
|
|
423
|
-
description: 'test task',
|
|
424
|
-
from: 'peer-1',
|
|
425
|
-
timestamp: Date.now(),
|
|
426
|
-
timeout: 30,
|
|
427
|
-
},
|
|
428
|
-
timestamp: Date.now(),
|
|
429
|
-
},
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
expect(response.status).toBe(500);
|
|
433
|
-
expect(response.body).toHaveProperty('error', 'Delegate failed');
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it('应该处理 onStatus handler 异常', async () => {
|
|
437
|
-
const errorHandler: WebhookHandler = {
|
|
438
|
-
onDiscover: vi.fn().mockResolvedValue({ capabilities: mockCapabilities }),
|
|
439
|
-
onDelegate: vi.fn().mockResolvedValue({ accepted: true, taskId: 'task-123' }),
|
|
440
|
-
onStatus: vi.fn().mockRejectedValue(new Error('Status check failed')),
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
server = new WebhookServer(testPort, errorHandler);
|
|
444
|
-
await server.start();
|
|
445
|
-
|
|
446
|
-
const response = await sendRequest({
|
|
447
|
-
body: {
|
|
448
|
-
type: 'status',
|
|
449
|
-
payload: {},
|
|
450
|
-
timestamp: Date.now(),
|
|
451
|
-
},
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
expect(response.status).toBe(500);
|
|
455
|
-
expect(response.body).toHaveProperty('error', 'Status check failed');
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
describe('事件处理测试', () => {
|
|
460
|
-
it('应该正确处理 discover 事件', async () => {
|
|
461
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
462
|
-
await server.start();
|
|
463
|
-
|
|
464
|
-
const response = await sendRequest({
|
|
465
|
-
body: {
|
|
466
|
-
type: 'discover',
|
|
467
|
-
payload: {
|
|
468
|
-
query: { capability: 'file-operation' },
|
|
469
|
-
requester: 'peer-123',
|
|
470
|
-
},
|
|
471
|
-
timestamp: Date.now(),
|
|
472
|
-
},
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
expect(response.status).toBe(200);
|
|
476
|
-
expect(mockHandler.onDiscover).toHaveBeenCalledWith({
|
|
477
|
-
query: { capability: 'file-operation' },
|
|
478
|
-
requester: 'peer-123',
|
|
479
|
-
});
|
|
480
|
-
expect(response.body).toEqual({
|
|
481
|
-
capabilities: mockCapabilities,
|
|
482
|
-
reputation: 80,
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('应该正确处理 delegate 事件', async () => {
|
|
487
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
488
|
-
await server.start();
|
|
489
|
-
|
|
490
|
-
const delegatePayload = {
|
|
491
|
-
taskId: 'task-abc',
|
|
492
|
-
taskType: 'file-read',
|
|
493
|
-
description: 'Read a file',
|
|
494
|
-
parameters: { path: '/test/file.txt' },
|
|
495
|
-
from: 'peer-456',
|
|
496
|
-
timestamp: Date.now(),
|
|
497
|
-
timeout: 60,
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const response = await sendRequest({
|
|
501
|
-
body: {
|
|
502
|
-
type: 'delegate',
|
|
503
|
-
payload: delegatePayload,
|
|
504
|
-
timestamp: Date.now(),
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
expect(response.status).toBe(200);
|
|
509
|
-
expect(mockHandler.onDelegate).toHaveBeenCalledWith(delegatePayload);
|
|
510
|
-
expect(response.body).toEqual({
|
|
511
|
-
accepted: true,
|
|
512
|
-
taskId: 'task-123',
|
|
513
|
-
});
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
it('应该正确处理 status 事件', async () => {
|
|
517
|
-
server = new WebhookServer(testPort, mockHandler);
|
|
518
|
-
await server.start();
|
|
519
|
-
|
|
520
|
-
const response = await sendRequest({
|
|
521
|
-
body: {
|
|
522
|
-
type: 'status',
|
|
523
|
-
payload: {},
|
|
524
|
-
timestamp: Date.now(),
|
|
525
|
-
},
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
expect(response.status).toBe(200);
|
|
529
|
-
expect(mockHandler.onStatus).toHaveBeenCalled();
|
|
530
|
-
expect(response.body).toEqual({
|
|
531
|
-
status: 'available',
|
|
532
|
-
load: 0.5,
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
describe('自定义配置测试', () => {
|
|
538
|
-
it('应该使用自定义请求体大小限制', async () => {
|
|
539
|
-
server = new WebhookServer(testPort, mockHandler, { maxBodySize: 1024 });
|
|
540
|
-
await server.start();
|
|
541
|
-
|
|
542
|
-
// 发送一个刚好在限制内的请求
|
|
543
|
-
const validBody = {
|
|
544
|
-
type: 'status',
|
|
545
|
-
payload: {},
|
|
546
|
-
timestamp: Date.now(),
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
const response = await sendRequest({ body: validBody });
|
|
550
|
-
expect(response.status).toBe(200);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
it('应该使用自定义允许来源列表', async () => {
|
|
554
|
-
server = new WebhookServer(testPort, mockHandler, {
|
|
555
|
-
allowedOrigins: ['http://custom-origin.com', 'http://another.com'],
|
|
556
|
-
});
|
|
557
|
-
await server.start();
|
|
558
|
-
|
|
559
|
-
const response = await sendRequest({
|
|
560
|
-
headers: { Origin: 'http://custom-origin.com' },
|
|
561
|
-
body: { type: 'status', payload: {}, timestamp: Date.now() },
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
expect(response.headers['access-control-allow-origin']).toBe('http://custom-origin.com');
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
it('空允许来源列表应该使用默认值', async () => {
|
|
568
|
-
server = new WebhookServer(testPort, mockHandler, { allowedOrigins: [] });
|
|
569
|
-
await server.start();
|
|
570
|
-
|
|
571
|
-
const response = await sendRequest({
|
|
572
|
-
headers: { Origin: 'http://some-origin.com' },
|
|
573
|
-
body: { type: 'status', payload: {}, timestamp: Date.now() },
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
// 空数组时,应该使用默认值 'http://localhost'
|
|
577
|
-
expect(response.headers['access-control-allow-origin']).toBe('http://localhost');
|
|
578
|
-
});
|
|
579
|
-
});
|
|
580
|
-
});
|