@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,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TaskQueue 边界、竞态和幂等性测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
-
import { TaskQueue } from './task-queue.js';
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
|
|
10
|
-
const TEST_DIR = './test-tmp';
|
|
11
|
-
|
|
12
|
-
describe('TaskQueue 边界问题', () => {
|
|
13
|
-
let queue: TaskQueue;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
if (!fs.existsSync(TEST_DIR)) {
|
|
17
|
-
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
queue = new TaskQueue({
|
|
20
|
-
maxSize: 5,
|
|
21
|
-
maxAgeMs: 1000,
|
|
22
|
-
persistDir: TEST_DIR,
|
|
23
|
-
persistEnabled: true
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
queue.close();
|
|
29
|
-
// 清理测试目录
|
|
30
|
-
if (fs.existsSync(TEST_DIR)) {
|
|
31
|
-
fs.rmSync(TEST_DIR, { recursive: true });
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('队列边界', () => {
|
|
36
|
-
it('应该在队列满时抛出错误', () => {
|
|
37
|
-
// 添加 5 个任务(达到上限)
|
|
38
|
-
for (let i = 0; i < 5; i++) {
|
|
39
|
-
queue.add({ taskId: `task-${i}`, taskType: 'test' });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 第 6 个应该失败
|
|
43
|
-
expect(() => queue.add({ taskId: 'task-6', taskType: 'test' }))
|
|
44
|
-
.toThrow('Task queue is full');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('应该在队列空时返回空数组', () => {
|
|
48
|
-
expect(queue.getPending()).toEqual([]);
|
|
49
|
-
expect(queue.getStats().pending).toBe(0);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('应该正确处理 null/undefined 参数', () => {
|
|
53
|
-
// @ts-expect-error 测试边界
|
|
54
|
-
expect(() => queue.add(null)).toThrow();
|
|
55
|
-
// @ts-expect-error 测试边界
|
|
56
|
-
expect(() => queue.add(undefined)).toThrow();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('任务过期清理', () => {
|
|
61
|
-
it('应该清理过期任务', async () => {
|
|
62
|
-
queue.add({ taskId: 'task-1', taskType: 'test' });
|
|
63
|
-
expect(queue.getStats().pending).toBe(1);
|
|
64
|
-
|
|
65
|
-
// 等待过期
|
|
66
|
-
await new Promise(r => setTimeout(r, 1100));
|
|
67
|
-
|
|
68
|
-
// 触发清理(通过添加新任务)
|
|
69
|
-
queue.add({ taskId: 'task-2', taskType: 'test' });
|
|
70
|
-
|
|
71
|
-
// 旧任务应该被清理
|
|
72
|
-
expect(queue.get('task-1')).toBeUndefined();
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('幂等性', () => {
|
|
77
|
-
it('应该覆盖相同 taskId 的任务', () => {
|
|
78
|
-
queue.add({ taskId: 'task-1', taskType: 'test', description: 'first' });
|
|
79
|
-
queue.add({ taskId: 'task-1', taskType: 'test', description: 'second' });
|
|
80
|
-
|
|
81
|
-
const task = queue.get('task-1');
|
|
82
|
-
expect(task?.description).toBe('second');
|
|
83
|
-
expect(queue.getStats().total).toBe(1); // 只有一个任务
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('应该正确处理重复的 markProcessing', () => {
|
|
87
|
-
queue.add({ taskId: 'task-1', taskType: 'test' });
|
|
88
|
-
|
|
89
|
-
const first = queue.markProcessing('task-1');
|
|
90
|
-
const second = queue.markProcessing('task-1');
|
|
91
|
-
|
|
92
|
-
expect(first?.status).toBe('processing');
|
|
93
|
-
expect(second?.status).toBe('processing');
|
|
94
|
-
expect(queue.getStats().processing).toBe(1);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('应该正确处理重复的 complete', () => {
|
|
98
|
-
queue.add({ taskId: 'task-1', taskType: 'test' });
|
|
99
|
-
|
|
100
|
-
queue.complete('task-1', { status: 'success', result: 'first' });
|
|
101
|
-
queue.complete('task-1', { status: 'success', result: 'second' });
|
|
102
|
-
|
|
103
|
-
const task = queue.get('task-1');
|
|
104
|
-
expect(task?.result).toBe('second');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe('SQLite 持久化', () => {
|
|
109
|
-
it('应该在关闭后保留任务', () => {
|
|
110
|
-
queue.add({ taskId: 'task-1', taskType: 'test' });
|
|
111
|
-
queue.add({ taskId: 'task-2', taskType: 'test' });
|
|
112
|
-
queue.markProcessing('task-1');
|
|
113
|
-
|
|
114
|
-
queue.close();
|
|
115
|
-
|
|
116
|
-
// 重新打开
|
|
117
|
-
const newQueue = new TaskQueue({
|
|
118
|
-
maxSize: 5,
|
|
119
|
-
persistDir: TEST_DIR,
|
|
120
|
-
persistEnabled: true
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// 恢复后 processing 任务被重置为 pending,避免僵尸任务
|
|
124
|
-
const stats = newQueue.getStats();
|
|
125
|
-
expect(stats.pending).toBe(2); // task-1 和 task-2
|
|
126
|
-
expect(stats.processing).toBe(0);
|
|
127
|
-
|
|
128
|
-
newQueue.close();
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('并发竞态条件', () => {
|
|
134
|
-
let queue: TaskQueue;
|
|
135
|
-
|
|
136
|
-
beforeEach(() => {
|
|
137
|
-
queue = new TaskQueue({ maxSize: 1000 });
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
afterEach(() => {
|
|
141
|
-
queue.close();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('应该正确处理批量添加', () => {
|
|
145
|
-
// add() 是同步方法,不需要 Promise.resolve 包装
|
|
146
|
-
for (let i = 0; i < 100; i++) {
|
|
147
|
-
queue.add({ taskId: `task-${i}`, taskType: 'test' });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
expect(queue.getStats().total).toBe(100);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('应该正确处理批量 add 和 getPending', () => {
|
|
154
|
-
// 同步操作,不需要 Promise 包装
|
|
155
|
-
for (let i = 0; i < 50; i++) {
|
|
156
|
-
queue.add({ taskId: `task-${i}`, taskType: 'test' });
|
|
157
|
-
queue.getPending();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 最终应该有 50 个任务
|
|
161
|
-
expect(queue.getStats().total).toBe(50);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('应该正确处理批量 complete', () => {
|
|
165
|
-
for (let i = 0; i < 50; i++) {
|
|
166
|
-
queue.add({ taskId: `task-${i}`, taskType: 'test' });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// complete() 是同步方法,不需要 Promise 包装
|
|
170
|
-
for (let i = 0; i < 50; i++) {
|
|
171
|
-
queue.complete(`task-${i}`, { status: 'success' });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
expect(queue.getStats().completed).toBe(50);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('应该正确处理 taskId 输入验证', () => {
|
|
178
|
-
// 空字符串应该抛出错误
|
|
179
|
-
expect(() => queue.add({ taskId: '', taskType: 'test' })).toThrow('taskId must be a non-empty string');
|
|
180
|
-
|
|
181
|
-
// 只有空格的字符串应该抛出错误
|
|
182
|
-
expect(() => queue.add({ taskId: ' ', taskType: 'test' })).toThrow('taskId must be a non-empty string');
|
|
183
|
-
|
|
184
|
-
// 正常的 taskId 应该成功
|
|
185
|
-
expect(() => queue.add({ taskId: 'valid-task', taskType: 'test' })).not.toThrow();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('应该保留重新添加任务的 createdAt 时间戳', () => {
|
|
189
|
-
queue.add({ taskId: 'task-1', taskType: 'test', description: 'first' });
|
|
190
|
-
const originalTask = queue.get('task-1');
|
|
191
|
-
const originalCreatedAt = originalTask?.createdAt;
|
|
192
|
-
|
|
193
|
-
// 等待一小段时间
|
|
194
|
-
const start = Date.now();
|
|
195
|
-
while (Date.now() - start < 10) { /* spin */ }
|
|
196
|
-
|
|
197
|
-
// 重新添加相同 taskId 的任务
|
|
198
|
-
queue.add({ taskId: 'task-1', taskType: 'test', description: 'second' });
|
|
199
|
-
const updatedTask = queue.get('task-1');
|
|
200
|
-
|
|
201
|
-
// createdAt 应该保持不变
|
|
202
|
-
expect(updatedTask?.createdAt).toBe(originalCreatedAt);
|
|
203
|
-
// 但 description 应该更新
|
|
204
|
-
expect(updatedTask?.description).toBe('second');
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// P1 修复:测试僵尸任务重置功能
|
|
209
|
-
describe('resetProcessingTask', () => {
|
|
210
|
-
let queue: TaskQueue;
|
|
211
|
-
|
|
212
|
-
beforeEach(() => {
|
|
213
|
-
if (!fs.existsSync(TEST_DIR)) {
|
|
214
|
-
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
215
|
-
}
|
|
216
|
-
queue = new TaskQueue({
|
|
217
|
-
maxSize: 5,
|
|
218
|
-
maxAgeMs: 1000,
|
|
219
|
-
persistDir: TEST_DIR,
|
|
220
|
-
persistEnabled: true
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
afterEach(() => {
|
|
225
|
-
queue.close();
|
|
226
|
-
if (fs.existsSync(TEST_DIR)) {
|
|
227
|
-
fs.rmSync(TEST_DIR, { recursive: true });
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('应该将 processing 任务重置为 pending', () => {
|
|
232
|
-
queue.add({ taskId: 'task-1', taskType: 'test', from: 'peer-1', timestamp: Date.now(), timeout: 30000 });
|
|
233
|
-
queue.markProcessing('task-1');
|
|
234
|
-
|
|
235
|
-
expect(queue.get('task-1')?.status).toBe('processing');
|
|
236
|
-
|
|
237
|
-
const result = queue.resetProcessingTask('task-1');
|
|
238
|
-
|
|
239
|
-
expect(result?.status).toBe('pending');
|
|
240
|
-
expect(queue.get('task-1')?.status).toBe('pending');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('应该只重置 processing 状态的任务', () => {
|
|
244
|
-
queue.add({ taskId: 'task-1', taskType: 'test', from: 'peer-1', timestamp: Date.now(), timeout: 30000 });
|
|
245
|
-
// 任务状态是 pending,不应该被重置
|
|
246
|
-
const result = queue.resetProcessingTask('task-1');
|
|
247
|
-
expect(result).toBeUndefined();
|
|
248
|
-
expect(queue.get('task-1')?.status).toBe('pending');
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('应该对不存在的任务返回 undefined', () => {
|
|
252
|
-
const result = queue.resetProcessingTask('non-existent');
|
|
253
|
-
expect(result).toBeUndefined();
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('应该更新 updatedAt 时间戳', () => {
|
|
257
|
-
queue.add({ taskId: 'task-1', taskType: 'test', from: 'peer-1', timestamp: Date.now(), timeout: 30000 });
|
|
258
|
-
queue.markProcessing('task-1');
|
|
259
|
-
|
|
260
|
-
const beforeReset = Date.now();
|
|
261
|
-
const result = queue.resetProcessingTask('task-1');
|
|
262
|
-
const afterReset = Date.now();
|
|
263
|
-
|
|
264
|
-
expect(result?.updatedAt).toBeGreaterThanOrEqual(beforeReset);
|
|
265
|
-
expect(result?.updatedAt).toBeLessThanOrEqual(afterReset);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('应该在持久化后保持重置状态', () => {
|
|
269
|
-
queue.add({ taskId: 'task-1', taskType: 'test', from: 'peer-1', timestamp: Date.now(), timeout: 30000 });
|
|
270
|
-
queue.markProcessing('task-1');
|
|
271
|
-
queue.resetProcessingTask('task-1');
|
|
272
|
-
queue.close();
|
|
273
|
-
|
|
274
|
-
// 创建新队列,应该恢复任务且状态为 pending
|
|
275
|
-
const newQueue = new TaskQueue({
|
|
276
|
-
maxSize: 5,
|
|
277
|
-
maxAgeMs: 1000,
|
|
278
|
-
persistDir: TEST_DIR,
|
|
279
|
-
persistEnabled: true
|
|
280
|
-
});
|
|
281
|
-
expect(newQueue.get('task-1')?.status).toBe('pending');
|
|
282
|
-
newQueue.close();
|
|
283
|
-
});
|
|
284
|
-
});
|
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TaskQueue 崩溃恢复测试
|
|
3
|
-
* 测试 SQLite 持久化和恢复
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import { TaskQueue } from './task-queue.js';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import Database from 'better-sqlite3';
|
|
11
|
-
import { randomUUID } from 'crypto';
|
|
12
|
-
|
|
13
|
-
describe('TaskQueue 崩溃恢复测试', () => {
|
|
14
|
-
let queue: TaskQueue;
|
|
15
|
-
let testDir: string;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
// 为每个测试使用唯一的目录,确保测试隔离
|
|
19
|
-
testDir = `./test-tmp-persistence-${randomUUID()}`;
|
|
20
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
21
|
-
queue = new TaskQueue({
|
|
22
|
-
maxSize: 1000,
|
|
23
|
-
maxAgeMs: 60000,
|
|
24
|
-
persistDir: testDir,
|
|
25
|
-
persistEnabled: true
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
try {
|
|
31
|
-
queue?.close();
|
|
32
|
-
} catch (e) {
|
|
33
|
-
// 忽略关闭错误
|
|
34
|
-
}
|
|
35
|
-
// 使用唯一目录,清理更可靠
|
|
36
|
-
if (testDir && fs.existsSync(testDir)) {
|
|
37
|
-
try {
|
|
38
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
39
|
-
} catch (e) {
|
|
40
|
-
// 忽略删除错误(可能文件被锁定)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe('基本持久化和恢复', () => {
|
|
46
|
-
it('应该在关闭后保留 pending 任务', () => {
|
|
47
|
-
queue.add({ taskId: 'pending-task-1', taskType: 'test', description: 'Pending task' });
|
|
48
|
-
queue.add({ taskId: 'pending-task-2', taskType: 'test', description: 'Pending task 2' });
|
|
49
|
-
|
|
50
|
-
queue.close();
|
|
51
|
-
|
|
52
|
-
// 重新打开
|
|
53
|
-
const newQueue = new TaskQueue({
|
|
54
|
-
maxSize: 1000,
|
|
55
|
-
persistDir: testDir,
|
|
56
|
-
persistEnabled: true
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const stats = newQueue.getStats();
|
|
60
|
-
expect(stats.pending).toBe(2);
|
|
61
|
-
|
|
62
|
-
const task1 = newQueue.get('pending-task-1');
|
|
63
|
-
expect(task1?.description).toBe('Pending task');
|
|
64
|
-
expect(task1?.status).toBe('pending');
|
|
65
|
-
|
|
66
|
-
newQueue.close();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('应该在关闭后保留 processing 任务(恢复为 pending)', () => {
|
|
70
|
-
queue.add({ taskId: 'processing-task', taskType: 'test' });
|
|
71
|
-
queue.markProcessing('processing-task');
|
|
72
|
-
|
|
73
|
-
queue.close();
|
|
74
|
-
|
|
75
|
-
const newQueue = new TaskQueue({
|
|
76
|
-
maxSize: 1000,
|
|
77
|
-
persistDir: testDir,
|
|
78
|
-
persistEnabled: true
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// 恢复后 processing 任务被重置为 pending,避免僵尸任务
|
|
82
|
-
const stats = newQueue.getStats();
|
|
83
|
-
expect(stats.pending).toBe(1);
|
|
84
|
-
expect(stats.processing).toBe(0);
|
|
85
|
-
|
|
86
|
-
const task = newQueue.get('processing-task');
|
|
87
|
-
expect(task?.status).toBe('pending');
|
|
88
|
-
|
|
89
|
-
newQueue.close();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('不应该恢复已完成任务', () => {
|
|
93
|
-
queue.add({ taskId: 'completed-task', taskType: 'test' });
|
|
94
|
-
queue.complete('completed-task', { status: 'success', result: 'done' });
|
|
95
|
-
|
|
96
|
-
queue.close();
|
|
97
|
-
|
|
98
|
-
const newQueue = new TaskQueue({
|
|
99
|
-
maxSize: 1000,
|
|
100
|
-
persistDir: testDir,
|
|
101
|
-
persistEnabled: true
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const stats = newQueue.getStats();
|
|
105
|
-
expect(stats.completed).toBe(0); // 已完成任务不恢复到内存
|
|
106
|
-
expect(stats.total).toBe(0);
|
|
107
|
-
|
|
108
|
-
newQueue.close();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('不应该恢复已失败任务', () => {
|
|
112
|
-
queue.add({ taskId: 'failed-task', taskType: 'test' });
|
|
113
|
-
queue.complete('failed-task', { status: 'error', error: 'Something went wrong' });
|
|
114
|
-
|
|
115
|
-
queue.close();
|
|
116
|
-
|
|
117
|
-
const newQueue = new TaskQueue({
|
|
118
|
-
maxSize: 1000,
|
|
119
|
-
persistDir: testDir,
|
|
120
|
-
persistEnabled: true
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const stats = newQueue.getStats();
|
|
124
|
-
expect(stats.failed).toBe(0);
|
|
125
|
-
expect(stats.total).toBe(0);
|
|
126
|
-
|
|
127
|
-
newQueue.close();
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('模拟崩溃场景', () => {
|
|
132
|
-
it('应该在未正常关闭后恢复数据', () => {
|
|
133
|
-
queue.add({ taskId: 'crash-task-1', taskType: 'test' });
|
|
134
|
-
queue.add({ taskId: 'crash-task-2', taskType: 'test' });
|
|
135
|
-
queue.markProcessing('crash-task-1');
|
|
136
|
-
|
|
137
|
-
// 模拟崩溃:不调用 close()
|
|
138
|
-
// 直接删除队列对象
|
|
139
|
-
// @ts-expect-error 模拟崩溃
|
|
140
|
-
queue = null;
|
|
141
|
-
|
|
142
|
-
// 强制 GC(如果可用)
|
|
143
|
-
if (global.gc) {
|
|
144
|
-
global.gc();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 重新打开
|
|
148
|
-
const newQueue = new TaskQueue({
|
|
149
|
-
maxSize: 1000,
|
|
150
|
-
persistDir: testDir,
|
|
151
|
-
persistEnabled: true
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// 恢复后 processing 任务被重置为 pending,避免僵尸任务
|
|
155
|
-
const stats = newQueue.getStats();
|
|
156
|
-
expect(stats.pending).toBe(2); // crash-task-1 和 crash-task-2
|
|
157
|
-
expect(stats.processing).toBe(0);
|
|
158
|
-
|
|
159
|
-
newQueue.close();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('应该在部分写入后恢复一致状态', () => {
|
|
163
|
-
// 直接操作数据库模拟部分写入
|
|
164
|
-
const dbPath = path.join(testDir, 'task-queue.db');
|
|
165
|
-
|
|
166
|
-
queue.add({ taskId: 'partial-1', taskType: 'test' });
|
|
167
|
-
|
|
168
|
-
// 在事务中途关闭队列
|
|
169
|
-
const db = new Database(dbPath);
|
|
170
|
-
db.exec('BEGIN');
|
|
171
|
-
db.prepare('INSERT INTO tasks (id, task_type, status, created_at) VALUES (?, ?, ?, ?)')
|
|
172
|
-
.run('partial-2', 'test', 'pending', Date.now());
|
|
173
|
-
// 不提交事务,直接关闭
|
|
174
|
-
db.close();
|
|
175
|
-
|
|
176
|
-
// 重新打开队列
|
|
177
|
-
const newQueue = new TaskQueue({
|
|
178
|
-
maxSize: 1000,
|
|
179
|
-
persistDir: testDir,
|
|
180
|
-
persistEnabled: true
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// partial-2 应该不在(事务未提交)
|
|
184
|
-
const task = newQueue.get('partial-2');
|
|
185
|
-
expect(task).toBeUndefined();
|
|
186
|
-
|
|
187
|
-
// partial-1 应该在
|
|
188
|
-
expect(newQueue.get('partial-1')).toBeDefined();
|
|
189
|
-
|
|
190
|
-
newQueue.close();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('应该处理数据库文件损坏', () => {
|
|
194
|
-
queue.add({ taskId: 'before-corrupt', taskType: 'test' });
|
|
195
|
-
queue.close();
|
|
196
|
-
|
|
197
|
-
// 损坏数据库文件
|
|
198
|
-
const dbPath = path.join(testDir, 'task-queue.db');
|
|
199
|
-
fs.writeFileSync(dbPath, 'corrupted data here');
|
|
200
|
-
|
|
201
|
-
// 应该能够创建新数据库
|
|
202
|
-
expect(() => {
|
|
203
|
-
const newQueue = new TaskQueue({
|
|
204
|
-
maxSize: 1000,
|
|
205
|
-
persistDir: testDir,
|
|
206
|
-
persistEnabled: true
|
|
207
|
-
});
|
|
208
|
-
newQueue.close();
|
|
209
|
-
}).not.toThrow();
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe('数据库状态恢复', () => {
|
|
214
|
-
it('应该恢复任务的完整状态', () => {
|
|
215
|
-
const taskData = {
|
|
216
|
-
taskId: 'full-state-task',
|
|
217
|
-
taskType: 'full-test',
|
|
218
|
-
description: 'Full state test',
|
|
219
|
-
parameters: { key: 'value', nested: { data: 123 } }
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
queue.add(taskData);
|
|
223
|
-
queue.markProcessing('full-state-task');
|
|
224
|
-
|
|
225
|
-
queue.close();
|
|
226
|
-
|
|
227
|
-
const newQueue = new TaskQueue({
|
|
228
|
-
maxSize: 1000,
|
|
229
|
-
persistDir: testDir,
|
|
230
|
-
persistEnabled: true
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const task = newQueue.get('full-state-task');
|
|
234
|
-
expect(task?.taskType).toBe('full-test');
|
|
235
|
-
expect(task?.description).toBe('Full state test');
|
|
236
|
-
expect(task?.parameters).toEqual({ key: 'value', nested: { data: 123 } });
|
|
237
|
-
// 恢复后 processing 任务被重置为 pending,避免僵尸任务
|
|
238
|
-
expect(task?.status).toBe('pending');
|
|
239
|
-
|
|
240
|
-
newQueue.close();
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('应该恢复 webhook 推送状态', () => {
|
|
244
|
-
queue.add({ taskId: 'webhook-state-task', taskType: 'webhook' });
|
|
245
|
-
queue.markWebhookPushed('webhook-state-task');
|
|
246
|
-
|
|
247
|
-
queue.close();
|
|
248
|
-
|
|
249
|
-
const newQueue = new TaskQueue({
|
|
250
|
-
maxSize: 1000,
|
|
251
|
-
persistDir: testDir,
|
|
252
|
-
persistEnabled: true
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
const task = newQueue.get('webhook-state-task');
|
|
256
|
-
expect(task?.webhookPushed).toBe(true);
|
|
257
|
-
|
|
258
|
-
newQueue.close();
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('应该恢复时间戳', () => {
|
|
262
|
-
const beforeAdd = Date.now();
|
|
263
|
-
queue.add({ taskId: 'timestamp-task', taskType: 'timestamp' });
|
|
264
|
-
queue.markProcessing('timestamp-task');
|
|
265
|
-
|
|
266
|
-
queue.close();
|
|
267
|
-
|
|
268
|
-
const newQueue = new TaskQueue({
|
|
269
|
-
maxSize: 1000,
|
|
270
|
-
persistDir: testDir,
|
|
271
|
-
persistEnabled: true
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
const task = newQueue.get('timestamp-task');
|
|
275
|
-
expect(task?.createdAt).toBeGreaterThanOrEqual(beforeAdd);
|
|
276
|
-
expect(task?.updatedAt).toBeDefined();
|
|
277
|
-
expect(task?.updatedAt).toBeGreaterThanOrEqual(beforeAdd);
|
|
278
|
-
|
|
279
|
-
newQueue.close();
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
describe('多实例恢复', () => {
|
|
284
|
-
it('应该在多实例间共享数据', () => {
|
|
285
|
-
queue.add({ taskId: 'shared-task', taskType: 'shared' });
|
|
286
|
-
queue.close();
|
|
287
|
-
|
|
288
|
-
// 同时打开多个实例(注意:实际中应该避免这种情况)
|
|
289
|
-
const queue1 = new TaskQueue({
|
|
290
|
-
maxSize: 1000,
|
|
291
|
-
persistDir: testDir,
|
|
292
|
-
persistEnabled: true
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
const task = queue1.get('shared-task');
|
|
296
|
-
expect(task).toBeDefined();
|
|
297
|
-
|
|
298
|
-
queue1.close();
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe('数据库清理', () => {
|
|
303
|
-
it('应该在恢复时忽略已删除的任务', () => {
|
|
304
|
-
queue.add({ taskId: 'to-delete', taskType: 'delete' });
|
|
305
|
-
queue.add({ taskId: 'to-keep', taskType: 'keep' });
|
|
306
|
-
queue.delete('to-delete');
|
|
307
|
-
|
|
308
|
-
queue.close();
|
|
309
|
-
|
|
310
|
-
const newQueue = new TaskQueue({
|
|
311
|
-
maxSize: 1000,
|
|
312
|
-
persistDir: testDir,
|
|
313
|
-
persistEnabled: true
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
expect(newQueue.get('to-delete')).toBeUndefined();
|
|
317
|
-
expect(newQueue.get('to-keep')).toBeDefined();
|
|
318
|
-
|
|
319
|
-
newQueue.close();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('应该正确处理大量恢复任务', () => {
|
|
323
|
-
// 添加大量任务
|
|
324
|
-
for (let i = 0; i < 100; i++) {
|
|
325
|
-
queue.add({ taskId: `bulk-task-${i}`, taskType: 'bulk' });
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// 标记一些为 processing
|
|
329
|
-
for (let i = 0; i < 10; i++) {
|
|
330
|
-
queue.markProcessing(`bulk-task-${i}`);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
queue.close();
|
|
334
|
-
|
|
335
|
-
const newQueue = new TaskQueue({
|
|
336
|
-
maxSize: 1000,
|
|
337
|
-
persistDir: testDir,
|
|
338
|
-
persistEnabled: true
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// 恢复后 processing 任务被重置为 pending,避免僵尸任务
|
|
342
|
-
const stats = newQueue.getStats();
|
|
343
|
-
expect(stats.pending).toBe(100);
|
|
344
|
-
expect(stats.processing).toBe(0);
|
|
345
|
-
expect(stats.total).toBe(100);
|
|
346
|
-
|
|
347
|
-
newQueue.close();
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
describe('错误恢复', () => {
|
|
352
|
-
it('应该处理无效的 JSON 数据', () => {
|
|
353
|
-
const dbPath = path.join(testDir, 'task-queue.db');
|
|
354
|
-
|
|
355
|
-
queue.add({ taskId: 'valid-task', taskType: 'test' });
|
|
356
|
-
queue.close();
|
|
357
|
-
|
|
358
|
-
// 插入无效 JSON
|
|
359
|
-
const db = new Database(dbPath);
|
|
360
|
-
db.prepare('INSERT INTO tasks (id, task_type, status, created_at, parameters) VALUES (?, ?, ?, ?, ?)')
|
|
361
|
-
.run('invalid-json-task', 'test', 'pending', Date.now(), '{ invalid json }');
|
|
362
|
-
db.close();
|
|
363
|
-
|
|
364
|
-
// 应该能够启动并跳过无效数据
|
|
365
|
-
const newQueue = new TaskQueue({
|
|
366
|
-
maxSize: 1000,
|
|
367
|
-
persistDir: testDir,
|
|
368
|
-
persistEnabled: true
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// 有效任务应该存在
|
|
372
|
-
expect(newQueue.get('valid-task')).toBeDefined();
|
|
373
|
-
|
|
374
|
-
newQueue.close();
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it('应该处理缺失的列', () => {
|
|
378
|
-
const dbPath = path.join(testDir, 'task-queue.db');
|
|
379
|
-
|
|
380
|
-
queue.close();
|
|
381
|
-
|
|
382
|
-
// 创建一个不同 schema 的表
|
|
383
|
-
const db = new Database(dbPath);
|
|
384
|
-
db.exec('DROP TABLE IF EXISTS tasks');
|
|
385
|
-
db.exec(`
|
|
386
|
-
CREATE TABLE tasks (
|
|
387
|
-
id TEXT PRIMARY KEY,
|
|
388
|
-
status TEXT NOT NULL,
|
|
389
|
-
created_at INTEGER NOT NULL
|
|
390
|
-
-- 缺少其他列
|
|
391
|
-
)
|
|
392
|
-
`);
|
|
393
|
-
db.prepare('INSERT INTO tasks (id, status, created_at) VALUES (?, ?, ?)')
|
|
394
|
-
.run('minimal-task', 'pending', Date.now());
|
|
395
|
-
db.close();
|
|
396
|
-
|
|
397
|
-
// 应该能够处理缺失的列
|
|
398
|
-
expect(() => {
|
|
399
|
-
const newQueue = new TaskQueue({
|
|
400
|
-
maxSize: 1000,
|
|
401
|
-
persistDir: testDir,
|
|
402
|
-
persistEnabled: true
|
|
403
|
-
});
|
|
404
|
-
newQueue.close();
|
|
405
|
-
}).not.toThrow();
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
});
|