@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.
Files changed (136) hide show
  1. package/package.json +8 -1
  2. package/.github/workflows/ci.yml +0 -113
  3. package/.github/workflows/publish.yml +0 -60
  4. package/MONOREPO.md +0 -58
  5. package/SKILL.md +0 -137
  6. package/dist/adapters/openclaw.d.ts +0 -103
  7. package/dist/adapters/openclaw.d.ts.map +0 -1
  8. package/dist/adapters/openclaw.js +0 -297
  9. package/dist/adapters/openclaw.js.map +0 -1
  10. package/dist/core/connection-manager.d.ts +0 -80
  11. package/dist/core/connection-manager.d.ts.map +0 -1
  12. package/dist/core/connection-manager.js +0 -235
  13. package/dist/core/connection-manager.js.map +0 -1
  14. package/dist/core/connection-manager.test.d.ts +0 -2
  15. package/dist/core/connection-manager.test.d.ts.map +0 -1
  16. package/dist/core/connection-manager.test.js +0 -52
  17. package/dist/core/connection-manager.test.js.map +0 -1
  18. package/dist/core/identity.d.ts +0 -47
  19. package/dist/core/identity.d.ts.map +0 -1
  20. package/dist/core/identity.js +0 -130
  21. package/dist/core/identity.js.map +0 -1
  22. package/dist/core/identity.test.d.ts +0 -2
  23. package/dist/core/identity.test.d.ts.map +0 -1
  24. package/dist/core/identity.test.js +0 -43
  25. package/dist/core/identity.test.js.map +0 -1
  26. package/dist/core/serverless.d.ts +0 -155
  27. package/dist/core/serverless.d.ts.map +0 -1
  28. package/dist/core/serverless.js +0 -615
  29. package/dist/core/serverless.js.map +0 -1
  30. package/dist/daemon/webhook.test.d.ts +0 -2
  31. package/dist/daemon/webhook.test.d.ts.map +0 -1
  32. package/dist/daemon/webhook.test.js +0 -24
  33. package/dist/daemon/webhook.test.js.map +0 -1
  34. package/dist/protocol/messages.d.ts +0 -739
  35. package/dist/protocol/messages.d.ts.map +0 -1
  36. package/dist/protocol/messages.js +0 -188
  37. package/dist/protocol/messages.js.map +0 -1
  38. package/dist/protocol/messages.test.d.ts +0 -2
  39. package/dist/protocol/messages.test.d.ts.map +0 -1
  40. package/dist/protocol/messages.test.js +0 -55
  41. package/dist/protocol/messages.test.js.map +0 -1
  42. package/docs/F2A-PROTOCOL.md +0 -61
  43. package/docs/MOBILE_BOOTSTRAP_DESIGN.md +0 -126
  44. package/docs/a2a-lessons.md +0 -316
  45. package/docs/middleware-guide.md +0 -448
  46. package/docs/readme-update-checklist.md +0 -90
  47. package/docs/reputation-guide.md +0 -396
  48. package/docs/rfcs/001-reputation-system.md +0 -712
  49. package/docs/security-design.md +0 -247
  50. package/install.sh +0 -231
  51. package/packages/openclaw-adapter/README.md +0 -510
  52. package/packages/openclaw-adapter/openclaw.plugin.json +0 -106
  53. package/packages/openclaw-adapter/package.json +0 -40
  54. package/packages/openclaw-adapter/src/announcement-queue.test.ts +0 -449
  55. package/packages/openclaw-adapter/src/announcement-queue.ts +0 -403
  56. package/packages/openclaw-adapter/src/capability-detector.test.ts +0 -99
  57. package/packages/openclaw-adapter/src/capability-detector.ts +0 -183
  58. package/packages/openclaw-adapter/src/claim-handlers.test.ts +0 -974
  59. package/packages/openclaw-adapter/src/claim-handlers.ts +0 -482
  60. package/packages/openclaw-adapter/src/connector.business.test.ts +0 -583
  61. package/packages/openclaw-adapter/src/connector.ts +0 -795
  62. package/packages/openclaw-adapter/src/index.test.ts +0 -82
  63. package/packages/openclaw-adapter/src/index.ts +0 -18
  64. package/packages/openclaw-adapter/src/integration.e2e.test.ts +0 -829
  65. package/packages/openclaw-adapter/src/logger.ts +0 -51
  66. package/packages/openclaw-adapter/src/network-client.test.ts +0 -266
  67. package/packages/openclaw-adapter/src/network-client.ts +0 -251
  68. package/packages/openclaw-adapter/src/network-recovery.test.ts +0 -465
  69. package/packages/openclaw-adapter/src/node-manager.test.ts +0 -136
  70. package/packages/openclaw-adapter/src/node-manager.ts +0 -429
  71. package/packages/openclaw-adapter/src/plugin.test.ts +0 -439
  72. package/packages/openclaw-adapter/src/plugin.ts +0 -104
  73. package/packages/openclaw-adapter/src/reputation.test.ts +0 -221
  74. package/packages/openclaw-adapter/src/reputation.ts +0 -368
  75. package/packages/openclaw-adapter/src/task-guard.test.ts +0 -502
  76. package/packages/openclaw-adapter/src/task-guard.ts +0 -860
  77. package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +0 -462
  78. package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +0 -284
  79. package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +0 -408
  80. package/packages/openclaw-adapter/src/task-queue.ts +0 -668
  81. package/packages/openclaw-adapter/src/tool-handlers.test.ts +0 -906
  82. package/packages/openclaw-adapter/src/tool-handlers.ts +0 -574
  83. package/packages/openclaw-adapter/src/types.ts +0 -361
  84. package/packages/openclaw-adapter/src/webhook-pusher.test.ts +0 -188
  85. package/packages/openclaw-adapter/src/webhook-pusher.ts +0 -220
  86. package/packages/openclaw-adapter/src/webhook-server.test.ts +0 -580
  87. package/packages/openclaw-adapter/src/webhook-server.ts +0 -202
  88. package/packages/openclaw-adapter/tsconfig.json +0 -20
  89. package/src/cli/commands.test.ts +0 -157
  90. package/src/cli/commands.ts +0 -129
  91. package/src/cli/index.test.ts +0 -77
  92. package/src/cli/index.ts +0 -234
  93. package/src/core/autonomous-economy.test.ts +0 -291
  94. package/src/core/autonomous-economy.ts +0 -428
  95. package/src/core/e2ee-crypto.test.ts +0 -125
  96. package/src/core/e2ee-crypto.ts +0 -246
  97. package/src/core/f2a.test.ts +0 -269
  98. package/src/core/f2a.ts +0 -618
  99. package/src/core/p2p-network.test.ts +0 -199
  100. package/src/core/p2p-network.ts +0 -1432
  101. package/src/core/reputation-security.test.ts +0 -403
  102. package/src/core/reputation-security.ts +0 -562
  103. package/src/core/reputation.test.ts +0 -260
  104. package/src/core/reputation.ts +0 -576
  105. package/src/core/review-committee.test.ts +0 -380
  106. package/src/core/review-committee.ts +0 -401
  107. package/src/core/token-manager.test.ts +0 -133
  108. package/src/core/token-manager.ts +0 -140
  109. package/src/daemon/control-server.test.ts +0 -216
  110. package/src/daemon/control-server.ts +0 -292
  111. package/src/daemon/index.test.ts +0 -85
  112. package/src/daemon/index.ts +0 -89
  113. package/src/daemon/main.ts +0 -44
  114. package/src/daemon/start.ts +0 -29
  115. package/src/daemon/webhook.test.ts +0 -68
  116. package/src/daemon/webhook.ts +0 -105
  117. package/src/index.test.ts +0 -436
  118. package/src/index.ts +0 -72
  119. package/src/types/index.test.ts +0 -87
  120. package/src/types/index.ts +0 -341
  121. package/src/types/result.ts +0 -68
  122. package/src/utils/benchmark.ts +0 -237
  123. package/src/utils/logger.ts +0 -331
  124. package/src/utils/middleware.ts +0 -229
  125. package/src/utils/rate-limiter.ts +0 -207
  126. package/src/utils/signature.ts +0 -136
  127. package/src/utils/validation.ts +0 -186
  128. package/tests/docker/Dockerfile.node +0 -23
  129. package/tests/docker/Dockerfile.runner +0 -18
  130. package/tests/docker/docker-compose.test.yml +0 -73
  131. package/tests/integration/message-passing.test.ts +0 -109
  132. package/tests/integration/multi-node.test.ts +0 -92
  133. package/tests/integration/p2p-connection.test.ts +0 -83
  134. package/tests/integration/test-config.ts +0 -32
  135. package/tsconfig.json +0 -21
  136. 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
- });