@f2a/network 0.1.2
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/.github/workflows/ci.yml +113 -0
- package/.github/workflows/publish.yml +60 -0
- package/LICENSE +21 -0
- package/MONOREPO.md +58 -0
- package/README.md +280 -0
- package/SKILL.md +137 -0
- package/dist/adapters/openclaw.d.ts +103 -0
- package/dist/adapters/openclaw.d.ts.map +1 -0
- package/dist/adapters/openclaw.js +297 -0
- package/dist/adapters/openclaw.js.map +1 -0
- package/dist/cli/commands.d.ts +17 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +107 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +203 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/autonomous-economy.d.ts +136 -0
- package/dist/core/autonomous-economy.d.ts.map +1 -0
- package/dist/core/autonomous-economy.js +255 -0
- package/dist/core/autonomous-economy.js.map +1 -0
- package/dist/core/connection-manager.d.ts +80 -0
- package/dist/core/connection-manager.d.ts.map +1 -0
- package/dist/core/connection-manager.js +235 -0
- package/dist/core/connection-manager.js.map +1 -0
- package/dist/core/connection-manager.test.d.ts +2 -0
- package/dist/core/connection-manager.test.d.ts.map +1 -0
- package/dist/core/connection-manager.test.js +52 -0
- package/dist/core/connection-manager.test.js.map +1 -0
- package/dist/core/e2ee-crypto.d.ts +90 -0
- package/dist/core/e2ee-crypto.d.ts.map +1 -0
- package/dist/core/e2ee-crypto.js +190 -0
- package/dist/core/e2ee-crypto.js.map +1 -0
- package/dist/core/f2a.d.ts +126 -0
- package/dist/core/f2a.d.ts.map +1 -0
- package/dist/core/f2a.js +425 -0
- package/dist/core/f2a.js.map +1 -0
- package/dist/core/identity.d.ts +47 -0
- package/dist/core/identity.d.ts.map +1 -0
- package/dist/core/identity.js +130 -0
- package/dist/core/identity.js.map +1 -0
- package/dist/core/identity.test.d.ts +2 -0
- package/dist/core/identity.test.d.ts.map +1 -0
- package/dist/core/identity.test.js +43 -0
- package/dist/core/identity.test.js.map +1 -0
- package/dist/core/p2p-network.d.ts +242 -0
- package/dist/core/p2p-network.d.ts.map +1 -0
- package/dist/core/p2p-network.js +1182 -0
- package/dist/core/p2p-network.js.map +1 -0
- package/dist/core/reputation-security.d.ts +168 -0
- package/dist/core/reputation-security.d.ts.map +1 -0
- package/dist/core/reputation-security.js +369 -0
- package/dist/core/reputation-security.js.map +1 -0
- package/dist/core/reputation.d.ts +179 -0
- package/dist/core/reputation.d.ts.map +1 -0
- package/dist/core/reputation.js +472 -0
- package/dist/core/reputation.js.map +1 -0
- package/dist/core/review-committee.d.ts +130 -0
- package/dist/core/review-committee.d.ts.map +1 -0
- package/dist/core/review-committee.js +251 -0
- package/dist/core/review-committee.js.map +1 -0
- package/dist/core/serverless.d.ts +155 -0
- package/dist/core/serverless.d.ts.map +1 -0
- package/dist/core/serverless.js +615 -0
- package/dist/core/serverless.js.map +1 -0
- package/dist/core/token-manager.d.ts +42 -0
- package/dist/core/token-manager.d.ts.map +1 -0
- package/dist/core/token-manager.js +122 -0
- package/dist/core/token-manager.js.map +1 -0
- package/dist/daemon/control-server.d.ts +55 -0
- package/dist/daemon/control-server.d.ts.map +1 -0
- package/dist/daemon/control-server.js +262 -0
- package/dist/daemon/control-server.js.map +1 -0
- package/dist/daemon/index.d.ts +35 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +69 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/main.d.ts +6 -0
- package/dist/daemon/main.d.ts.map +1 -0
- package/dist/daemon/main.js +38 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/start.d.ts +6 -0
- package/dist/daemon/start.d.ts.map +1 -0
- package/dist/daemon/start.js +25 -0
- package/dist/daemon/start.js.map +1 -0
- package/dist/daemon/webhook.d.ts +30 -0
- package/dist/daemon/webhook.d.ts.map +1 -0
- package/dist/daemon/webhook.js +86 -0
- package/dist/daemon/webhook.js.map +1 -0
- package/dist/daemon/webhook.test.d.ts +2 -0
- package/dist/daemon/webhook.test.d.ts.map +1 -0
- package/dist/daemon/webhook.test.js +24 -0
- package/dist/daemon/webhook.test.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/messages.d.ts +739 -0
- package/dist/protocol/messages.d.ts.map +1 -0
- package/dist/protocol/messages.js +188 -0
- package/dist/protocol/messages.js.map +1 -0
- package/dist/protocol/messages.test.d.ts +2 -0
- package/dist/protocol/messages.test.d.ts.map +1 -0
- package/dist/protocol/messages.test.js +55 -0
- package/dist/protocol/messages.test.js.map +1 -0
- package/dist/types/index.d.ts +247 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/result.d.ts +28 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +16 -0
- package/dist/types/result.js.map +1 -0
- package/dist/utils/benchmark.d.ts +67 -0
- package/dist/utils/benchmark.d.ts.map +1 -0
- package/dist/utils/benchmark.js +179 -0
- package/dist/utils/benchmark.js.map +1 -0
- package/dist/utils/logger.d.ts +105 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +275 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/middleware.d.ts +85 -0
- package/dist/utils/middleware.d.ts.map +1 -0
- package/dist/utils/middleware.js +173 -0
- package/dist/utils/middleware.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +71 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +160 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/signature.d.ts +57 -0
- package/dist/utils/signature.d.ts.map +1 -0
- package/dist/utils/signature.js +102 -0
- package/dist/utils/signature.js.map +1 -0
- package/dist/utils/validation.d.ts +504 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +159 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/F2A-PROTOCOL.md +61 -0
- package/docs/MOBILE_BOOTSTRAP_DESIGN.md +126 -0
- package/docs/a2a-lessons.md +316 -0
- package/docs/middleware-guide.md +448 -0
- package/docs/readme-update-checklist.md +90 -0
- package/docs/reputation-guide.md +396 -0
- package/docs/rfcs/001-reputation-system.md +712 -0
- package/docs/security-design.md +247 -0
- package/install.sh +231 -0
- package/package.json +64 -0
- package/packages/openclaw-adapter/README.md +510 -0
- package/packages/openclaw-adapter/openclaw.plugin.json +106 -0
- package/packages/openclaw-adapter/package.json +40 -0
- package/packages/openclaw-adapter/src/announcement-queue.test.ts +449 -0
- package/packages/openclaw-adapter/src/announcement-queue.ts +403 -0
- package/packages/openclaw-adapter/src/capability-detector.test.ts +99 -0
- package/packages/openclaw-adapter/src/capability-detector.ts +183 -0
- package/packages/openclaw-adapter/src/claim-handlers.test.ts +974 -0
- package/packages/openclaw-adapter/src/claim-handlers.ts +482 -0
- package/packages/openclaw-adapter/src/connector.business.test.ts +583 -0
- package/packages/openclaw-adapter/src/connector.ts +795 -0
- package/packages/openclaw-adapter/src/index.test.ts +82 -0
- package/packages/openclaw-adapter/src/index.ts +18 -0
- package/packages/openclaw-adapter/src/integration.e2e.test.ts +829 -0
- package/packages/openclaw-adapter/src/logger.ts +51 -0
- package/packages/openclaw-adapter/src/network-client.test.ts +266 -0
- package/packages/openclaw-adapter/src/network-client.ts +251 -0
- package/packages/openclaw-adapter/src/network-recovery.test.ts +465 -0
- package/packages/openclaw-adapter/src/node-manager.test.ts +136 -0
- package/packages/openclaw-adapter/src/node-manager.ts +429 -0
- package/packages/openclaw-adapter/src/plugin.test.ts +439 -0
- package/packages/openclaw-adapter/src/plugin.ts +104 -0
- package/packages/openclaw-adapter/src/reputation.test.ts +221 -0
- package/packages/openclaw-adapter/src/reputation.ts +368 -0
- package/packages/openclaw-adapter/src/task-guard.test.ts +502 -0
- package/packages/openclaw-adapter/src/task-guard.ts +860 -0
- package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +462 -0
- package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +284 -0
- package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +408 -0
- package/packages/openclaw-adapter/src/task-queue.ts +668 -0
- package/packages/openclaw-adapter/src/tool-handlers.test.ts +906 -0
- package/packages/openclaw-adapter/src/tool-handlers.ts +574 -0
- package/packages/openclaw-adapter/src/types.ts +361 -0
- package/packages/openclaw-adapter/src/webhook-pusher.test.ts +188 -0
- package/packages/openclaw-adapter/src/webhook-pusher.ts +220 -0
- package/packages/openclaw-adapter/src/webhook-server.test.ts +580 -0
- package/packages/openclaw-adapter/src/webhook-server.ts +202 -0
- package/packages/openclaw-adapter/tsconfig.json +20 -0
- package/src/cli/commands.test.ts +157 -0
- package/src/cli/commands.ts +129 -0
- package/src/cli/index.test.ts +77 -0
- package/src/cli/index.ts +234 -0
- package/src/core/autonomous-economy.test.ts +291 -0
- package/src/core/autonomous-economy.ts +428 -0
- package/src/core/e2ee-crypto.test.ts +125 -0
- package/src/core/e2ee-crypto.ts +246 -0
- package/src/core/f2a.test.ts +269 -0
- package/src/core/f2a.ts +618 -0
- package/src/core/p2p-network.test.ts +199 -0
- package/src/core/p2p-network.ts +1432 -0
- package/src/core/reputation-security.test.ts +403 -0
- package/src/core/reputation-security.ts +562 -0
- package/src/core/reputation.test.ts +260 -0
- package/src/core/reputation.ts +576 -0
- package/src/core/review-committee.test.ts +380 -0
- package/src/core/review-committee.ts +401 -0
- package/src/core/token-manager.test.ts +133 -0
- package/src/core/token-manager.ts +140 -0
- package/src/daemon/control-server.test.ts +216 -0
- package/src/daemon/control-server.ts +292 -0
- package/src/daemon/index.test.ts +85 -0
- package/src/daemon/index.ts +89 -0
- package/src/daemon/main.ts +44 -0
- package/src/daemon/start.ts +29 -0
- package/src/daemon/webhook.test.ts +68 -0
- package/src/daemon/webhook.ts +105 -0
- package/src/index.test.ts +436 -0
- package/src/index.ts +72 -0
- package/src/types/index.test.ts +87 -0
- package/src/types/index.ts +341 -0
- package/src/types/result.ts +68 -0
- package/src/utils/benchmark.ts +237 -0
- package/src/utils/logger.ts +331 -0
- package/src/utils/middleware.ts +229 -0
- package/src/utils/rate-limiter.ts +207 -0
- package/src/utils/signature.ts +136 -0
- package/src/utils/validation.ts +186 -0
- package/tests/docker/Dockerfile.node +23 -0
- package/tests/docker/Dockerfile.runner +18 -0
- package/tests/docker/docker-compose.test.yml +73 -0
- package/tests/integration/message-passing.test.ts +109 -0
- package/tests/integration/multi-node.test.ts +92 -0
- package/tests/integration/p2p-connection.test.ts +83 -0
- package/tests/integration/test-config.ts +32 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F2A 端到端集成测试
|
|
3
|
+
* 测试完整的业务流程,使用真实的 SQLite 和 HTTP 服务器
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
import { F2AOpenClawAdapter } from './connector.js';
|
|
8
|
+
import { TaskQueue } from './task-queue.js';
|
|
9
|
+
import { ReputationSystem } from './reputation.js';
|
|
10
|
+
import { AnnouncementQueue } from './announcement-queue.js';
|
|
11
|
+
import { WebhookServer, WebhookHandler } from './webhook-server.js';
|
|
12
|
+
import { TaskGuard } from './task-guard.js';
|
|
13
|
+
import { createServer, IncomingMessage, ServerResponse } from 'http';
|
|
14
|
+
import { mkdirSync, rmSync, existsSync, writeFileSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { randomUUID } from 'crypto';
|
|
17
|
+
|
|
18
|
+
// 测试配置 - 使用随机后缀避免冲突
|
|
19
|
+
let TEST_DATA_DIR = `/tmp/f2a-integration-test-${Date.now()}`;
|
|
20
|
+
|
|
21
|
+
// 辅助函数:获取可用端口(确保端口真正可用)
|
|
22
|
+
async function getAvailablePort(): Promise<number> {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const tempServer = createServer();
|
|
25
|
+
tempServer.listen(0, () => {
|
|
26
|
+
const address = tempServer.address();
|
|
27
|
+
if (address && typeof address === 'object') {
|
|
28
|
+
const port = address.port;
|
|
29
|
+
tempServer.close(() => {
|
|
30
|
+
// 等待端口完全释放
|
|
31
|
+
setTimeout(() => resolve(port), 50);
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
reject(new Error('Failed to get port'));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 辅助函数:等待端口释放
|
|
41
|
+
async function waitForPortRelease(port: number, maxWait: number = 1000): Promise<void> {
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
while (Date.now() - startTime < maxWait) {
|
|
44
|
+
try {
|
|
45
|
+
await new Promise<void>((resolve, reject) => {
|
|
46
|
+
const req = require('http').request({
|
|
47
|
+
hostname: 'localhost',
|
|
48
|
+
port,
|
|
49
|
+
path: '/',
|
|
50
|
+
method: 'HEAD',
|
|
51
|
+
timeout: 100,
|
|
52
|
+
}, () => resolve());
|
|
53
|
+
req.on('error', () => resolve());
|
|
54
|
+
req.end();
|
|
55
|
+
});
|
|
56
|
+
await new Promise(r => setTimeout(r, 100));
|
|
57
|
+
} catch {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 辅助函数:发送 HTTP 请求
|
|
64
|
+
async function sendRequest(options: {
|
|
65
|
+
port: number;
|
|
66
|
+
method?: string;
|
|
67
|
+
path?: string;
|
|
68
|
+
headers?: Record<string, string>;
|
|
69
|
+
body?: unknown;
|
|
70
|
+
}): Promise<{ status: number; body: unknown }> {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const req = {
|
|
73
|
+
hostname: 'localhost',
|
|
74
|
+
port: options.port,
|
|
75
|
+
path: options.path || '/webhook',
|
|
76
|
+
method: options.method || 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
...options.headers,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const httpReq = require('http').request(req, (res: any) => {
|
|
84
|
+
let data = '';
|
|
85
|
+
res.on('data', (chunk: Buffer) => (data += chunk));
|
|
86
|
+
res.on('end', () => {
|
|
87
|
+
try {
|
|
88
|
+
const body = data ? JSON.parse(data) : {};
|
|
89
|
+
resolve({ status: res.statusCode, body });
|
|
90
|
+
} catch {
|
|
91
|
+
resolve({ status: res.statusCode, body: data });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
httpReq.on('error', reject);
|
|
97
|
+
if (options.body) {
|
|
98
|
+
httpReq.write(JSON.stringify(options.body));
|
|
99
|
+
}
|
|
100
|
+
httpReq.end();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe('F2A 端到端集成测试', () => {
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
// 每个测试使用独立的目录
|
|
107
|
+
TEST_DATA_DIR = `/tmp/f2a-integration-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
108
|
+
// 创建测试目录
|
|
109
|
+
mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
afterEach(async () => {
|
|
113
|
+
// 等待端口释放
|
|
114
|
+
await new Promise(r => setTimeout(r, 100));
|
|
115
|
+
// 清理测试目录
|
|
116
|
+
try {
|
|
117
|
+
rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
118
|
+
} catch {
|
|
119
|
+
// 忽略清理错误
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('任务委托完整流程', () => {
|
|
124
|
+
it('应该完成从任务入队 → Webhook 推送 → 处理 → 提交结果的完整流程', async () => {
|
|
125
|
+
const port = await getAvailablePort();
|
|
126
|
+
const taskQueue = new TaskQueue({
|
|
127
|
+
maxSize: 100,
|
|
128
|
+
persistDir: TEST_DATA_DIR,
|
|
129
|
+
persistEnabled: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 记录收到的推送
|
|
133
|
+
let receivedTask: any = null;
|
|
134
|
+
|
|
135
|
+
// 创建模拟的 webhook 接收服务器
|
|
136
|
+
const webhookReceiver = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
137
|
+
let body = '';
|
|
138
|
+
req.on('data', (chunk) => (body += chunk));
|
|
139
|
+
req.on('end', () => {
|
|
140
|
+
try {
|
|
141
|
+
receivedTask = JSON.parse(body);
|
|
142
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
143
|
+
res.end(JSON.stringify({ success: true }));
|
|
144
|
+
} catch {
|
|
145
|
+
res.writeHead(500);
|
|
146
|
+
res.end();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await new Promise<void>((resolve) => webhookReceiver.listen(port + 1, () => resolve()));
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// 等待服务器完全启动
|
|
155
|
+
await new Promise(r => setTimeout(r, 50));
|
|
156
|
+
|
|
157
|
+
// 1. 任务入队
|
|
158
|
+
const task = taskQueue.add({
|
|
159
|
+
taskId: 'e2e-task-1',
|
|
160
|
+
taskType: 'code-generation',
|
|
161
|
+
description: 'Write a hello world function',
|
|
162
|
+
from: 'peer-requester',
|
|
163
|
+
timestamp: Date.now(),
|
|
164
|
+
timeout: 60000,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(task.status).toBe('pending');
|
|
168
|
+
expect(task.taskId).toBe('e2e-task-1');
|
|
169
|
+
|
|
170
|
+
// 2. 模拟 Webhook 推送
|
|
171
|
+
const pendingTasks = taskQueue.getWebhookPending();
|
|
172
|
+
expect(pendingTasks.length).toBe(1);
|
|
173
|
+
|
|
174
|
+
// 模拟推送到 webhook 接收器
|
|
175
|
+
const pushResult = await fetch(`http://localhost:${port + 1}/webhook`, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
body: JSON.stringify(pendingTasks[0]),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(pushResult.ok).toBe(true);
|
|
182
|
+
|
|
183
|
+
// 等待接收
|
|
184
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
185
|
+
expect(receivedTask).not.toBeNull();
|
|
186
|
+
expect(receivedTask.taskId).toBe('e2e-task-1');
|
|
187
|
+
|
|
188
|
+
// 标记为已推送
|
|
189
|
+
taskQueue.markWebhookPushed('e2e-task-1');
|
|
190
|
+
|
|
191
|
+
// 3. 模拟处理任务
|
|
192
|
+
taskQueue.markProcessing('e2e-task-1');
|
|
193
|
+
const processingTask = taskQueue.get('e2e-task-1');
|
|
194
|
+
expect(processingTask?.status).toBe('processing');
|
|
195
|
+
|
|
196
|
+
// 4. 提交结果
|
|
197
|
+
taskQueue.complete('e2e-task-1', {
|
|
198
|
+
taskId: 'e2e-task-1',
|
|
199
|
+
status: 'success',
|
|
200
|
+
result: 'function hello() { console.log("Hello, World!"); }',
|
|
201
|
+
latency: 1500,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const completedTask = taskQueue.get('e2e-task-1');
|
|
205
|
+
expect(completedTask?.status).toBe('completed');
|
|
206
|
+
expect(completedTask?.result).toContain('Hello, World!');
|
|
207
|
+
|
|
208
|
+
// 验证统计
|
|
209
|
+
const stats = taskQueue.getStats();
|
|
210
|
+
expect(stats.completed).toBe(1);
|
|
211
|
+
expect(stats.pending).toBe(0);
|
|
212
|
+
} finally {
|
|
213
|
+
taskQueue.close();
|
|
214
|
+
await new Promise<void>((resolve) => webhookReceiver.close(() => resolve()));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('应该正确处理任务失败场景', async () => {
|
|
219
|
+
const taskQueue = new TaskQueue({
|
|
220
|
+
maxSize: 100,
|
|
221
|
+
persistDir: TEST_DATA_DIR,
|
|
222
|
+
persistEnabled: true,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// 添加任务
|
|
227
|
+
taskQueue.add({
|
|
228
|
+
taskId: 'failing-task',
|
|
229
|
+
taskType: 'test',
|
|
230
|
+
description: 'This task will fail',
|
|
231
|
+
from: 'peer-1',
|
|
232
|
+
timestamp: Date.now(),
|
|
233
|
+
timeout: 30000,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// 标记处理中
|
|
237
|
+
taskQueue.markProcessing('failing-task');
|
|
238
|
+
|
|
239
|
+
// 提交失败结果
|
|
240
|
+
taskQueue.complete('failing-task', {
|
|
241
|
+
taskId: 'failing-task',
|
|
242
|
+
status: 'error',
|
|
243
|
+
error: 'Something went wrong',
|
|
244
|
+
latency: 500,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const task = taskQueue.get('failing-task');
|
|
248
|
+
expect(task?.status).toBe('failed');
|
|
249
|
+
expect(task?.error).toBe('Something went wrong');
|
|
250
|
+
|
|
251
|
+
const stats = taskQueue.getStats();
|
|
252
|
+
expect(stats.failed).toBe(1);
|
|
253
|
+
} finally {
|
|
254
|
+
taskQueue.close();
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('信誉系统端到端', () => {
|
|
260
|
+
it('应该在任务成功后更新信誉并影响权限', async () => {
|
|
261
|
+
const reputationSystem = new ReputationSystem(
|
|
262
|
+
{
|
|
263
|
+
enabled: true,
|
|
264
|
+
initialScore: 50,
|
|
265
|
+
minScoreForService: 20,
|
|
266
|
+
decayRate: 0.01,
|
|
267
|
+
},
|
|
268
|
+
TEST_DATA_DIR
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const peerId = 'peer-test-success';
|
|
272
|
+
|
|
273
|
+
// 初始信誉检查
|
|
274
|
+
let rep = reputationSystem.getReputation(peerId);
|
|
275
|
+
expect(rep.score).toBe(50);
|
|
276
|
+
expect(reputationSystem.isAllowed(peerId)).toBe(true);
|
|
277
|
+
|
|
278
|
+
// 模拟任务成功
|
|
279
|
+
reputationSystem.recordSuccess(peerId, 'task-1', 1000);
|
|
280
|
+
reputationSystem.recordSuccess(peerId, 'task-2', 800);
|
|
281
|
+
|
|
282
|
+
rep = reputationSystem.getReputation(peerId);
|
|
283
|
+
expect(rep.score).toBeGreaterThan(50);
|
|
284
|
+
expect(rep.successfulTasks).toBe(2);
|
|
285
|
+
expect(rep.avgResponseTime).toBeGreaterThan(0);
|
|
286
|
+
|
|
287
|
+
// 多次失败降低信誉
|
|
288
|
+
for (let i = 0; i < 5; i++) {
|
|
289
|
+
reputationSystem.recordFailure(peerId, `fail-task-${i}`, 'Error');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
rep = reputationSystem.getReputation(peerId);
|
|
293
|
+
// 50 + 10 + 10 - 20*5 = 50 + 20 - 100 = -30 -> 0
|
|
294
|
+
expect(rep.score).toBe(0);
|
|
295
|
+
expect(reputationSystem.isAllowed(peerId)).toBe(false);
|
|
296
|
+
|
|
297
|
+
// 刷新确保持久化
|
|
298
|
+
reputationSystem.flush();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('应该在持久化后恢复信誉数据', async () => {
|
|
302
|
+
const peerId = 'peer-persistence-test';
|
|
303
|
+
|
|
304
|
+
// 创建并写入数据
|
|
305
|
+
{
|
|
306
|
+
const reputationSystem = new ReputationSystem(
|
|
307
|
+
{
|
|
308
|
+
enabled: true,
|
|
309
|
+
initialScore: 50,
|
|
310
|
+
minScoreForService: 20,
|
|
311
|
+
decayRate: 0.01,
|
|
312
|
+
},
|
|
313
|
+
TEST_DATA_DIR
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
reputationSystem.recordSuccess(peerId, 'task-1', 500);
|
|
317
|
+
reputationSystem.flush();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 重新加载验证持久化
|
|
321
|
+
{
|
|
322
|
+
const reputationSystem = new ReputationSystem(
|
|
323
|
+
{
|
|
324
|
+
enabled: true,
|
|
325
|
+
initialScore: 50,
|
|
326
|
+
minScoreForService: 20,
|
|
327
|
+
decayRate: 0.01,
|
|
328
|
+
},
|
|
329
|
+
TEST_DATA_DIR
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const rep = reputationSystem.getReputation(peerId);
|
|
333
|
+
expect(rep.score).toBeGreaterThan(50);
|
|
334
|
+
expect(rep.successfulTasks).toBe(1);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('Webhook 推送流程', () => {
|
|
340
|
+
it('应该正确处理 Webhook 推送失败后的重试', async () => {
|
|
341
|
+
const port = await getAvailablePort();
|
|
342
|
+
let requestCount = 0;
|
|
343
|
+
let successAfterAttempts = 3;
|
|
344
|
+
|
|
345
|
+
const webhookServer = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
346
|
+
requestCount++;
|
|
347
|
+
if (requestCount < successAfterAttempts) {
|
|
348
|
+
// 前两次返回错误
|
|
349
|
+
res.writeHead(503);
|
|
350
|
+
res.end(JSON.stringify({ error: 'Service unavailable' }));
|
|
351
|
+
} else {
|
|
352
|
+
// 第三次成功
|
|
353
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
354
|
+
res.end(JSON.stringify({ success: true }));
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
await new Promise<void>((resolve) => webhookServer.listen(port + 2, () => resolve()));
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const taskQueue = new TaskQueue({
|
|
362
|
+
maxSize: 100,
|
|
363
|
+
persistDir: TEST_DATA_DIR,
|
|
364
|
+
persistEnabled: true,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
taskQueue.add({
|
|
368
|
+
taskId: 'retry-task',
|
|
369
|
+
taskType: 'test',
|
|
370
|
+
description: 'Test webhook retry',
|
|
371
|
+
from: 'peer-1',
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
timeout: 30000,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// 模拟重试逻辑
|
|
377
|
+
let lastSuccess = false;
|
|
378
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
379
|
+
const result = await fetch(`http://localhost:${port + 2}/webhook`, {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
headers: { 'Content-Type': 'application/json' },
|
|
382
|
+
body: JSON.stringify({ taskId: 'retry-task' }),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (result.ok) {
|
|
386
|
+
lastSuccess = true;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
expect(lastSuccess).toBe(true);
|
|
392
|
+
expect(requestCount).toBe(successAfterAttempts);
|
|
393
|
+
|
|
394
|
+
taskQueue.close();
|
|
395
|
+
} finally {
|
|
396
|
+
await new Promise<void>((resolve) => webhookServer.close(() => resolve()));
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe('崩溃恢复流程', () => {
|
|
402
|
+
it('应该在崩溃后恢复未处理的任务', async () => {
|
|
403
|
+
// 第一次:创建任务并模拟崩溃(不正常关闭)
|
|
404
|
+
{
|
|
405
|
+
const taskQueue = new TaskQueue({
|
|
406
|
+
maxSize: 100,
|
|
407
|
+
persistDir: TEST_DATA_DIR,
|
|
408
|
+
persistEnabled: true,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
taskQueue.add({
|
|
412
|
+
taskId: 'crash-task-1',
|
|
413
|
+
taskType: 'important',
|
|
414
|
+
description: 'Important task that should survive crash',
|
|
415
|
+
from: 'peer-critical',
|
|
416
|
+
timestamp: Date.now(),
|
|
417
|
+
timeout: 60000,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
taskQueue.add({
|
|
421
|
+
taskId: 'crash-task-2',
|
|
422
|
+
taskType: 'important',
|
|
423
|
+
description: 'Another important task',
|
|
424
|
+
from: 'peer-critical',
|
|
425
|
+
timestamp: Date.now(),
|
|
426
|
+
timeout: 60000,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// 标记一个为 processing
|
|
430
|
+
taskQueue.markProcessing('crash-task-1');
|
|
431
|
+
|
|
432
|
+
// 不调用 close(),模拟崩溃
|
|
433
|
+
// 直接清空内存引用
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 第二次:重新打开,验证恢复
|
|
437
|
+
{
|
|
438
|
+
const taskQueue = new TaskQueue({
|
|
439
|
+
maxSize: 100,
|
|
440
|
+
persistDir: TEST_DATA_DIR,
|
|
441
|
+
persistEnabled: true,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// 等待恢复
|
|
445
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
446
|
+
|
|
447
|
+
const stats = taskQueue.getStats();
|
|
448
|
+
// 两个任务都应该恢复为 pending(processing 会被重置)
|
|
449
|
+
expect(stats.pending).toBe(2);
|
|
450
|
+
expect(stats.processing).toBe(0);
|
|
451
|
+
|
|
452
|
+
// 验证任务内容
|
|
453
|
+
const task1 = taskQueue.get('crash-task-1');
|
|
454
|
+
expect(task1?.description).toContain('Important task');
|
|
455
|
+
expect(task1?.status).toBe('pending'); // processing 被重置为 pending
|
|
456
|
+
|
|
457
|
+
const task2 = taskQueue.get('crash-task-2');
|
|
458
|
+
expect(task2?.description).toContain('Another important task');
|
|
459
|
+
|
|
460
|
+
taskQueue.close();
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('应该正确处理数据库损坏场景', async () => {
|
|
465
|
+
// 创建一个损坏的数据库文件
|
|
466
|
+
const dbPath = join(TEST_DATA_DIR, 'task-queue.db');
|
|
467
|
+
writeFileSync(dbPath, 'corrupted data that is not valid sqlite');
|
|
468
|
+
|
|
469
|
+
// 应该能够重建数据库
|
|
470
|
+
const taskQueue = new TaskQueue({
|
|
471
|
+
maxSize: 100,
|
|
472
|
+
persistDir: TEST_DATA_DIR,
|
|
473
|
+
persistEnabled: true,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// 应该可以正常添加任务
|
|
477
|
+
const task = taskQueue.add({
|
|
478
|
+
taskId: 'after-corrupt',
|
|
479
|
+
taskType: 'test',
|
|
480
|
+
description: 'Task after corruption recovery',
|
|
481
|
+
from: 'peer-1',
|
|
482
|
+
timestamp: Date.now(),
|
|
483
|
+
timeout: 30000,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
expect(task.taskId).toBe('after-corrupt');
|
|
487
|
+
|
|
488
|
+
taskQueue.close();
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe('并发场景', () => {
|
|
493
|
+
it('应该正确处理多个 Agent 同时委托任务', async () => {
|
|
494
|
+
const taskQueue = new TaskQueue({
|
|
495
|
+
maxSize: 1000,
|
|
496
|
+
persistDir: TEST_DATA_DIR,
|
|
497
|
+
persistEnabled: true,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
// 模拟 10 个 Agent 同时委托任务
|
|
502
|
+
const agentCount = 10;
|
|
503
|
+
const tasksPerAgent = 5;
|
|
504
|
+
|
|
505
|
+
const addPromises: Promise<void>[] = [];
|
|
506
|
+
|
|
507
|
+
for (let agent = 0; agent < agentCount; agent++) {
|
|
508
|
+
for (let task = 0; task < tasksPerAgent; task++) {
|
|
509
|
+
addPromises.push(
|
|
510
|
+
new Promise<void>((resolve) => {
|
|
511
|
+
setImmediate(() => {
|
|
512
|
+
try {
|
|
513
|
+
taskQueue.add({
|
|
514
|
+
taskId: `agent-${agent}-task-${task}`,
|
|
515
|
+
taskType: 'concurrent-test',
|
|
516
|
+
description: `Task from agent ${agent}`,
|
|
517
|
+
from: `peer-agent-${agent}`,
|
|
518
|
+
timestamp: Date.now(),
|
|
519
|
+
timeout: 30000,
|
|
520
|
+
});
|
|
521
|
+
} catch {
|
|
522
|
+
// 忽略错误
|
|
523
|
+
}
|
|
524
|
+
resolve();
|
|
525
|
+
});
|
|
526
|
+
})
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
await Promise.all(addPromises);
|
|
532
|
+
|
|
533
|
+
const stats = taskQueue.getStats();
|
|
534
|
+
expect(stats.pending).toBe(agentCount * tasksPerAgent);
|
|
535
|
+
expect(stats.total).toBe(agentCount * tasksPerAgent);
|
|
536
|
+
|
|
537
|
+
// 验证每个任务都能被正确获取
|
|
538
|
+
for (let agent = 0; agent < agentCount; agent++) {
|
|
539
|
+
for (let task = 0; task < tasksPerAgent; task++) {
|
|
540
|
+
const t = taskQueue.get(`agent-${agent}-task-${task}`);
|
|
541
|
+
expect(t).toBeDefined();
|
|
542
|
+
expect(t?.from).toBe(`peer-agent-${agent}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} finally {
|
|
546
|
+
taskQueue.close();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('应该正确处理同一任务的并发操作', async () => {
|
|
551
|
+
const taskQueue = new TaskQueue({
|
|
552
|
+
maxSize: 100,
|
|
553
|
+
persistDir: TEST_DATA_DIR,
|
|
554
|
+
persistEnabled: true,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
taskQueue.add({
|
|
559
|
+
taskId: 'concurrent-ops-task',
|
|
560
|
+
taskType: 'test',
|
|
561
|
+
description: 'Task with concurrent operations',
|
|
562
|
+
from: 'peer-1',
|
|
563
|
+
timestamp: Date.now(),
|
|
564
|
+
timeout: 30000,
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// 并发执行多种操作
|
|
568
|
+
const operations = [
|
|
569
|
+
// 尝试标记为 processing
|
|
570
|
+
() => taskQueue.markProcessing('concurrent-ops-task'),
|
|
571
|
+
// 尝试读取
|
|
572
|
+
() => taskQueue.get('concurrent-ops-task'),
|
|
573
|
+
// 再次标记(幂等性测试)
|
|
574
|
+
() => taskQueue.markProcessing('concurrent-ops-task'),
|
|
575
|
+
];
|
|
576
|
+
|
|
577
|
+
const results = await Promise.all(operations.map((op) => Promise.resolve(op())));
|
|
578
|
+
|
|
579
|
+
// 任务应该处于一致的状态
|
|
580
|
+
const task = taskQueue.get('concurrent-ops-task');
|
|
581
|
+
expect(task).toBeDefined();
|
|
582
|
+
expect(['pending', 'processing']).toContain(task?.status);
|
|
583
|
+
} finally {
|
|
584
|
+
taskQueue.close();
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
describe('AnnouncementQueue 端到端', () => {
|
|
590
|
+
it('应该完成从发布广播 → 认领 → 接受 → 委托的完整流程', async () => {
|
|
591
|
+
const announcementQueue = new AnnouncementQueue({
|
|
592
|
+
maxSize: 50,
|
|
593
|
+
maxAgeMs: 30 * 60 * 1000,
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
try {
|
|
597
|
+
// 1. 发布者创建任务广播
|
|
598
|
+
const announcement = announcementQueue.create({
|
|
599
|
+
taskType: 'code-review',
|
|
600
|
+
description: 'Review pull request #123',
|
|
601
|
+
requiredCapabilities: ['typescript', 'code-review'],
|
|
602
|
+
estimatedComplexity: 5,
|
|
603
|
+
reward: 100,
|
|
604
|
+
timeout: 300000,
|
|
605
|
+
from: 'peer-publisher',
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
expect(announcement.status).toBe('open');
|
|
609
|
+
expect(announcement.announcementId).toMatch(/^ann-/);
|
|
610
|
+
|
|
611
|
+
// 2. 多个 Agent 认领任务
|
|
612
|
+
const claim1 = announcementQueue.submitClaim(announcement.announcementId, {
|
|
613
|
+
claimant: 'peer-worker-1',
|
|
614
|
+
claimantName: 'Worker Alpha',
|
|
615
|
+
estimatedTime: 120000,
|
|
616
|
+
confidence: 0.85,
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
const claim2 = announcementQueue.submitClaim(announcement.announcementId, {
|
|
620
|
+
claimant: 'peer-worker-2',
|
|
621
|
+
claimantName: 'Worker Beta',
|
|
622
|
+
estimatedTime: 90000,
|
|
623
|
+
confidence: 0.95,
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
expect(claim1).toBeDefined();
|
|
627
|
+
expect(claim2).toBeDefined();
|
|
628
|
+
expect(announcement.claims).toHaveLength(2);
|
|
629
|
+
|
|
630
|
+
// 3. 发布者查看认领列表
|
|
631
|
+
const myAnnouncements = announcementQueue.getMyAnnouncements('peer-publisher');
|
|
632
|
+
expect(myAnnouncements).toHaveLength(1);
|
|
633
|
+
expect(myAnnouncements[0].claims).toHaveLength(2);
|
|
634
|
+
|
|
635
|
+
// 4. 发布者接受一个认领
|
|
636
|
+
const acceptedClaim = announcementQueue.acceptClaim(
|
|
637
|
+
announcement.announcementId,
|
|
638
|
+
claim2!.claimId
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
expect(acceptedClaim?.status).toBe('accepted');
|
|
642
|
+
expect(announcement.status).toBe('claimed');
|
|
643
|
+
|
|
644
|
+
// 其他认领应该被拒绝
|
|
645
|
+
const rejectedClaim = announcementQueue.get(announcement.announcementId)?.claims?.find(
|
|
646
|
+
(c) => c.claimId === claim1?.claimId
|
|
647
|
+
);
|
|
648
|
+
expect(rejectedClaim?.status).toBe('rejected');
|
|
649
|
+
|
|
650
|
+
// 5. 标记为已委托
|
|
651
|
+
announcementQueue.markDelegated(announcement.announcementId);
|
|
652
|
+
expect(announcement.status).toBe('delegated');
|
|
653
|
+
|
|
654
|
+
// 验证统计
|
|
655
|
+
const stats = announcementQueue.getStats();
|
|
656
|
+
expect(stats.delegated).toBe(1);
|
|
657
|
+
} finally {
|
|
658
|
+
announcementQueue.clear();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('应该正确处理广播过期场景', async () => {
|
|
663
|
+
const fastExpireQueue = new AnnouncementQueue({
|
|
664
|
+
maxSize: 50,
|
|
665
|
+
maxAgeMs: 100, // 100ms 过期
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
const announcement = fastExpireQueue.create({
|
|
670
|
+
taskType: 'quick-task',
|
|
671
|
+
description: 'This will expire soon',
|
|
672
|
+
timeout: 100,
|
|
673
|
+
from: 'peer-1',
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
expect(announcement.status).toBe('open');
|
|
677
|
+
|
|
678
|
+
// 等待过期
|
|
679
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
680
|
+
|
|
681
|
+
// 触发清理(通过创建新任务)
|
|
682
|
+
fastExpireQueue.create({
|
|
683
|
+
taskType: 'new-task',
|
|
684
|
+
description: 'New task',
|
|
685
|
+
timeout: 1000,
|
|
686
|
+
from: 'peer-2',
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
const stats = fastExpireQueue.getStats();
|
|
690
|
+
expect(stats.expired).toBe(1);
|
|
691
|
+
} finally {
|
|
692
|
+
fastExpireQueue.clear();
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
describe('TaskGuard 集成测试', () => {
|
|
698
|
+
it('应该在完整流程中正确应用安全规则', async () => {
|
|
699
|
+
const guard = new TaskGuard();
|
|
700
|
+
|
|
701
|
+
// 正常任务应该通过
|
|
702
|
+
const normalTask = {
|
|
703
|
+
taskId: 'normal-task',
|
|
704
|
+
taskType: 'code-generation',
|
|
705
|
+
description: 'Write a function to sort an array',
|
|
706
|
+
from: 'peer-1',
|
|
707
|
+
timestamp: Date.now(),
|
|
708
|
+
timeout: 30000,
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
let report = guard.check(normalTask);
|
|
712
|
+
expect(report.passed).toBe(true);
|
|
713
|
+
|
|
714
|
+
// 危险任务应该被阻止
|
|
715
|
+
const dangerousTask = {
|
|
716
|
+
taskId: 'dangerous-task',
|
|
717
|
+
taskType: 'shell',
|
|
718
|
+
description: 'rm -rf /',
|
|
719
|
+
from: 'peer-2',
|
|
720
|
+
timestamp: Date.now(),
|
|
721
|
+
timeout: 30000,
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
report = guard.check(dangerousTask);
|
|
725
|
+
expect(report.passed).toBe(false);
|
|
726
|
+
expect(report.blocks.some((b) => b.ruleId === 'dangerous-keywords')).toBe(true);
|
|
727
|
+
|
|
728
|
+
// 低信誉用户执行敏感操作应该被阻止
|
|
729
|
+
const sensitiveTask = {
|
|
730
|
+
taskId: 'sensitive-task',
|
|
731
|
+
taskType: 'file-operation',
|
|
732
|
+
description: 'Delete old logs',
|
|
733
|
+
from: 'peer-3',
|
|
734
|
+
timestamp: Date.now(),
|
|
735
|
+
timeout: 30000,
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
report = guard.check(sensitiveTask, {
|
|
739
|
+
requesterReputation: {
|
|
740
|
+
peerId: 'peer-3',
|
|
741
|
+
score: 10, // 非常低的信誉
|
|
742
|
+
successfulTasks: 1,
|
|
743
|
+
failedTasks: 20,
|
|
744
|
+
totalTasks: 21,
|
|
745
|
+
avgResponseTime: 5000,
|
|
746
|
+
lastInteraction: Date.now(),
|
|
747
|
+
history: [],
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// 检查是否因为低信誉而被阻止或警告
|
|
752
|
+
// 注意:具体行为取决于 TaskGuard 的配置
|
|
753
|
+
expect(report.results.some((r) => r.ruleId === 'reputation')).toBe(true);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
describe('WebhookServer 真实 HTTP 测试', () => {
|
|
758
|
+
it('应该正确处理真实的 HTTP 请求', async () => {
|
|
759
|
+
const port = await getAvailablePort();
|
|
760
|
+
|
|
761
|
+
const handler: WebhookHandler = {
|
|
762
|
+
onDiscover: async (payload) => ({
|
|
763
|
+
capabilities: [{ name: 'test-capability', description: 'Test capability', parameters: {} }],
|
|
764
|
+
reputation: 80,
|
|
765
|
+
}),
|
|
766
|
+
onDelegate: async (payload) => ({
|
|
767
|
+
accepted: true,
|
|
768
|
+
taskId: payload.taskId,
|
|
769
|
+
}),
|
|
770
|
+
onStatus: async () => ({
|
|
771
|
+
status: 'available',
|
|
772
|
+
load: 0.5,
|
|
773
|
+
}),
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
const server = new WebhookServer(port, handler);
|
|
777
|
+
await server.start();
|
|
778
|
+
|
|
779
|
+
try {
|
|
780
|
+
// 测试 discover
|
|
781
|
+
const discoverRes = await sendRequest({
|
|
782
|
+
port,
|
|
783
|
+
body: {
|
|
784
|
+
type: 'discover',
|
|
785
|
+
payload: { query: {}, requester: 'test-peer' },
|
|
786
|
+
timestamp: Date.now(),
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
expect(discoverRes.status).toBe(200);
|
|
790
|
+
expect((discoverRes.body as any).capabilities).toBeDefined();
|
|
791
|
+
|
|
792
|
+
// 测试 delegate
|
|
793
|
+
const delegateRes = await sendRequest({
|
|
794
|
+
port,
|
|
795
|
+
body: {
|
|
796
|
+
type: 'delegate',
|
|
797
|
+
payload: {
|
|
798
|
+
taskId: 'http-test-task',
|
|
799
|
+
taskType: 'test',
|
|
800
|
+
description: 'Test via HTTP',
|
|
801
|
+
from: 'test-peer',
|
|
802
|
+
timestamp: Date.now(),
|
|
803
|
+
timeout: 30000,
|
|
804
|
+
},
|
|
805
|
+
timestamp: Date.now(),
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
expect(delegateRes.status).toBe(200);
|
|
809
|
+
expect((delegateRes.body as any).accepted).toBe(true);
|
|
810
|
+
|
|
811
|
+
// 测试 status
|
|
812
|
+
const statusRes = await sendRequest({
|
|
813
|
+
port,
|
|
814
|
+
body: {
|
|
815
|
+
type: 'status',
|
|
816
|
+
payload: {},
|
|
817
|
+
timestamp: Date.now(),
|
|
818
|
+
},
|
|
819
|
+
});
|
|
820
|
+
expect(statusRes.status).toBe(200);
|
|
821
|
+
expect((statusRes.body as any).status).toBe('available');
|
|
822
|
+
} finally {
|
|
823
|
+
await server.stop();
|
|
824
|
+
// 等待端口释放
|
|
825
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
});
|