@f2a/network 0.1.2 → 0.2.0
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/README.md +278 -63
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +29 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/config.d.ts +176 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +386 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/daemon.d.ts +54 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +572 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/index.js +90 -16
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +13 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +352 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/core/e2ee-crypto.d.ts +127 -1
- package/dist/core/e2ee-crypto.d.ts.map +1 -1
- package/dist/core/e2ee-crypto.js +446 -12
- package/dist/core/e2ee-crypto.js.map +1 -1
- package/dist/core/f2a.d.ts +2 -1
- package/dist/core/f2a.d.ts.map +1 -1
- package/dist/core/f2a.js +6 -2
- package/dist/core/f2a.js.map +1 -1
- package/dist/core/identity/encrypted-key-store.d.ts +19 -0
- package/dist/core/identity/encrypted-key-store.d.ts.map +1 -0
- package/dist/core/identity/encrypted-key-store.js +72 -0
- package/dist/core/identity/encrypted-key-store.js.map +1 -0
- package/dist/core/identity/identity-manager.d.ts +133 -0
- package/dist/core/identity/identity-manager.d.ts.map +1 -0
- package/dist/core/identity/identity-manager.js +454 -0
- package/dist/core/identity/identity-manager.js.map +1 -0
- package/dist/core/identity/index.d.ts +8 -0
- package/dist/core/identity/index.d.ts.map +1 -0
- package/dist/core/identity/index.js +7 -0
- package/dist/core/identity/index.js.map +1 -0
- package/dist/core/identity/types.d.ts +70 -0
- package/dist/core/identity/types.d.ts.map +1 -0
- package/dist/core/identity/types.js +17 -0
- package/dist/core/identity/types.js.map +1 -0
- package/dist/core/p2p-network.d.ts +26 -0
- package/dist/core/p2p-network.d.ts.map +1 -1
- package/dist/core/p2p-network.js +434 -105
- package/dist/core/p2p-network.js.map +1 -1
- package/dist/core/reputation-security.d.ts +15 -0
- package/dist/core/reputation-security.d.ts.map +1 -1
- package/dist/core/reputation-security.js +73 -3
- package/dist/core/reputation-security.js.map +1 -1
- package/dist/core/reputation.d.ts +129 -4
- package/dist/core/reputation.d.ts.map +1 -1
- package/dist/core/reputation.js +294 -1
- package/dist/core/reputation.js.map +1 -1
- package/dist/core/review-committee.d.ts +2 -2
- package/dist/core/review-committee.d.ts.map +1 -1
- package/dist/core/review-committee.js +17 -0
- package/dist/core/review-committee.js.map +1 -1
- package/dist/daemon/control-server.d.ts.map +1 -1
- package/dist/daemon/control-server.js +44 -1
- package/dist/daemon/control-server.js.map +1 -1
- package/dist/daemon/webhook.d.ts +3 -0
- package/dist/daemon/webhook.d.ts.map +1 -1
- package/dist/daemon/webhook.js +318 -6
- package/dist/daemon/webhook.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/result.d.ts +1 -1
- package/dist/types/result.d.ts.map +1 -1
- package/dist/types/result.js.map +1 -1
- package/dist/utils/crypto-utils.d.ts +17 -0
- package/dist/utils/crypto-utils.d.ts.map +1 -0
- package/dist/utils/crypto-utils.js +28 -0
- package/dist/utils/crypto-utils.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +9 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/rate-limiter.d.ts.map +1 -1
- package/dist/utils/rate-limiter.js +3 -1
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/utils/signature.d.ts +47 -1
- package/dist/utils/signature.d.ts.map +1 -1
- package/dist/utils/signature.js +166 -11
- package/dist/utils/signature.js.map +1 -1
- package/package.json +9 -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
package/src/daemon/main.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* F2A Daemon 入口
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { F2ADaemon } from './index.js';
|
|
7
|
-
|
|
8
|
-
// 解析引导节点地址
|
|
9
|
-
const bootstrapPeers = process.env.BOOTSTRAP_PEERS
|
|
10
|
-
? process.env.BOOTSTRAP_PEERS.split(',')
|
|
11
|
-
: undefined;
|
|
12
|
-
|
|
13
|
-
// P2P 端口(默认 0 = 随机分配)
|
|
14
|
-
const p2pPort = parseInt(process.env.F2A_P2P_PORT || '0');
|
|
15
|
-
|
|
16
|
-
const daemon = new F2ADaemon({
|
|
17
|
-
controlPort: parseInt(process.env.F2A_CONTROL_PORT || '9001'),
|
|
18
|
-
network: {
|
|
19
|
-
listenPort: p2pPort,
|
|
20
|
-
bootstrapPeers,
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// 处理信号
|
|
25
|
-
process.on('SIGINT', async () => {
|
|
26
|
-
console.log('[Daemon] Received SIGINT, shutting down...');
|
|
27
|
-
await daemon.stop();
|
|
28
|
-
process.exit(0);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
process.on('SIGTERM', async () => {
|
|
32
|
-
console.log('[Daemon] Received SIGTERM, shutting down...');
|
|
33
|
-
await daemon.stop();
|
|
34
|
-
process.exit(0);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// 启动
|
|
38
|
-
daemon.start().catch((error) => {
|
|
39
|
-
console.error('[Daemon] Failed to start:', error instanceof Error ? error.message : String(error));
|
|
40
|
-
if (error instanceof Error && error.stack) {
|
|
41
|
-
console.error('[Daemon] Stack trace:', error.stack);
|
|
42
|
-
}
|
|
43
|
-
process.exit(1);
|
|
44
|
-
});
|
package/src/daemon/start.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* F2A Daemon 入口
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { F2ADaemon } from './index.js';
|
|
7
|
-
|
|
8
|
-
const daemon = new F2ADaemon({
|
|
9
|
-
controlPort: parseInt(process.env.F2A_CONTROL_PORT || '9001'),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
// 处理信号
|
|
13
|
-
process.on('SIGINT', async () => {
|
|
14
|
-
console.log('[Daemon] Received SIGINT, shutting down...');
|
|
15
|
-
await daemon.stop();
|
|
16
|
-
process.exit(0);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
process.on('SIGTERM', async () => {
|
|
20
|
-
console.log('[Daemon] Received SIGTERM, shutting down...');
|
|
21
|
-
await daemon.stop();
|
|
22
|
-
process.exit(0);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// 启动
|
|
26
|
-
daemon.start().catch((error) => {
|
|
27
|
-
console.error('[Daemon] Failed to start:', error);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { WebhookService } from './webhook.js';
|
|
3
|
-
|
|
4
|
-
describe('WebhookService', () => {
|
|
5
|
-
let service: WebhookService;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
service = new WebhookService({
|
|
9
|
-
url: 'http://localhost:8080/hooks',
|
|
10
|
-
token: 'test-token'
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('constructor', () => {
|
|
15
|
-
it('should create service with default options', () => {
|
|
16
|
-
const defaultService = new WebhookService({
|
|
17
|
-
url: 'http://localhost:8080/hooks',
|
|
18
|
-
token: 'test-token'
|
|
19
|
-
});
|
|
20
|
-
expect(defaultService).toBeDefined();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should create service with custom options', () => {
|
|
24
|
-
const customService = new WebhookService({
|
|
25
|
-
url: 'http://localhost:8080/hooks',
|
|
26
|
-
token: 'test-token',
|
|
27
|
-
timeout: 10000,
|
|
28
|
-
retries: 5,
|
|
29
|
-
retryDelay: 2000
|
|
30
|
-
});
|
|
31
|
-
expect(customService).toBeDefined();
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('send', () => {
|
|
36
|
-
it('should skip notification when token not set', async () => {
|
|
37
|
-
const noTokenService = new WebhookService({
|
|
38
|
-
url: 'http://localhost:8080/hooks',
|
|
39
|
-
token: ''
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const result = await noTokenService.send({ message: 'test' });
|
|
43
|
-
expect(result.success).toBe(false);
|
|
44
|
-
expect(result.error).toBe('Token not set');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should send notification with default options', async () => {
|
|
48
|
-
// This would make actual HTTP request in real scenario
|
|
49
|
-
// For unit test, we just verify it doesn't throw
|
|
50
|
-
const result = await service.send({ message: 'test' });
|
|
51
|
-
// Result depends on network
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should send notification with custom options', async () => {
|
|
55
|
-
const result = await service.send({
|
|
56
|
-
message: 'test message',
|
|
57
|
-
name: 'Test Agent',
|
|
58
|
-
wakeMode: 'now',
|
|
59
|
-
deliver: true
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should handle network errors with retries', async () => {
|
|
64
|
-
// Mock would be needed to test retry logic
|
|
65
|
-
const result = await service.send({ message: 'test' });
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
});
|
package/src/daemon/webhook.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Webhook 通知服务
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { request, RequestOptions } from 'https';
|
|
6
|
-
import { request as httpRequest } from 'http';
|
|
7
|
-
import { WebhookConfig } from '../types/index.js';
|
|
8
|
-
|
|
9
|
-
export interface WebhookNotification {
|
|
10
|
-
message: string;
|
|
11
|
-
name?: string;
|
|
12
|
-
wakeMode?: 'now' | 'next-heartbeat';
|
|
13
|
-
deliver?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class WebhookService {
|
|
17
|
-
private config: WebhookConfig;
|
|
18
|
-
|
|
19
|
-
constructor(config: WebhookConfig) {
|
|
20
|
-
this.config = {
|
|
21
|
-
timeout: 5000,
|
|
22
|
-
retries: 3,
|
|
23
|
-
retryDelay: 1000,
|
|
24
|
-
...config
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 发送通知
|
|
30
|
-
*/
|
|
31
|
-
async send(notification: WebhookNotification): Promise<{ success: boolean; error?: string }> {
|
|
32
|
-
if (!this.config.token) {
|
|
33
|
-
console.log('[Webhook] Token not set, skipping notification');
|
|
34
|
-
return { success: false, error: 'Token not set' };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const payload = JSON.stringify({
|
|
38
|
-
message: notification.message,
|
|
39
|
-
name: notification.name || 'F2A',
|
|
40
|
-
wakeMode: notification.wakeMode || 'now',
|
|
41
|
-
deliver: notification.deliver !== false
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
for (let attempt = 1; attempt <= this.config.retries!; attempt++) {
|
|
45
|
-
try {
|
|
46
|
-
await this.sendRequest(payload);
|
|
47
|
-
console.log(`[Webhook] Notification sent (attempt ${attempt})`);
|
|
48
|
-
return { success: true };
|
|
49
|
-
} catch (err) {
|
|
50
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
-
console.log(`[Webhook] Attempt ${attempt} failed: ${message}`);
|
|
52
|
-
|
|
53
|
-
if (attempt < this.config.retries!) {
|
|
54
|
-
await this.delay(this.config.retryDelay!);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { success: false, error: 'All retries failed' };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 发送 HTTP 请求
|
|
64
|
-
*/
|
|
65
|
-
private sendRequest(payload: string): Promise<void> {
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
const isHttps = this.config.url.startsWith('https');
|
|
68
|
-
const client = isHttps ? request : httpRequest;
|
|
69
|
-
|
|
70
|
-
const options: RequestOptions = {
|
|
71
|
-
method: 'POST',
|
|
72
|
-
headers: {
|
|
73
|
-
'Authorization': `Bearer ${this.config.token}`,
|
|
74
|
-
'Content-Type': 'application/json',
|
|
75
|
-
'Content-Length': Buffer.byteLength(payload)
|
|
76
|
-
},
|
|
77
|
-
timeout: this.config.timeout
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const req = client(this.config.url, options, (res) => {
|
|
81
|
-
if (res.statusCode === 200 || res.statusCode === 202) {
|
|
82
|
-
resolve();
|
|
83
|
-
} else {
|
|
84
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
req.on('error', reject);
|
|
89
|
-
req.on('timeout', () => {
|
|
90
|
-
req.destroy();
|
|
91
|
-
reject(new Error('Timeout'));
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
req.write(payload);
|
|
95
|
-
req.end();
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 延迟
|
|
101
|
-
*/
|
|
102
|
-
private delay(ms: number): Promise<void> {
|
|
103
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
104
|
-
}
|
|
105
|
-
}
|
package/src/index.test.ts
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
3
|
-
import {
|
|
4
|
-
// 核心 P2P 网络
|
|
5
|
-
F2A,
|
|
6
|
-
P2PNetwork,
|
|
7
|
-
TokenManager,
|
|
8
|
-
E2EECrypto,
|
|
9
|
-
// 信誉系统
|
|
10
|
-
ReputationManager,
|
|
11
|
-
REPUTATION_TIERS,
|
|
12
|
-
ReviewCommittee,
|
|
13
|
-
AutonomousEconomy,
|
|
14
|
-
// 信誉安全机制
|
|
15
|
-
ChainSignatureManager,
|
|
16
|
-
InvitationManager,
|
|
17
|
-
ChallengeManager,
|
|
18
|
-
// 工具模块
|
|
19
|
-
Logger,
|
|
20
|
-
RateLimiter,
|
|
21
|
-
RequestSigner,
|
|
22
|
-
createMessageSizeLimitMiddleware,
|
|
23
|
-
createMessageTypeFilterMiddleware,
|
|
24
|
-
// 类型
|
|
25
|
-
type ReputationLevel,
|
|
26
|
-
type MiddlewareContext,
|
|
27
|
-
// 版本号
|
|
28
|
-
VERSION,
|
|
29
|
-
} from './index.js';
|
|
30
|
-
|
|
31
|
-
// ============================================================================
|
|
32
|
-
// 业务场景 1: SDK 导出完整性验证
|
|
33
|
-
// 确保用户可以通过 SDK 正确导入所有需要的模块
|
|
34
|
-
// ============================================================================
|
|
35
|
-
describe('SDK Exports - 用户使用场景', () => {
|
|
36
|
-
describe('场景 1.1: 用户创建 F2A 节点', () => {
|
|
37
|
-
it('应该能导入 F2A 主类并创建实例', () => {
|
|
38
|
-
expect(F2A).toBeDefined();
|
|
39
|
-
expect(typeof F2A.create).toBe('function');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('应该能导入 P2PNetwork 用于底层网络操作', () => {
|
|
43
|
-
expect(P2PNetwork).toBeDefined();
|
|
44
|
-
expect(typeof P2PNetwork).toBe('function');
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('场景 1.2: 用户配置安全机制', () => {
|
|
49
|
-
it('应该能导入 TokenManager 进行认证管理', () => {
|
|
50
|
-
expect(TokenManager).toBeDefined();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('应该能导入 E2EECrypto 进行加密通信', () => {
|
|
54
|
-
expect(E2EECrypto).toBeDefined();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('应该能导入 RequestSigner 进行请求签名', () => {
|
|
58
|
-
expect(RequestSigner).toBeDefined();
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('场景 1.3: 用户实现信誉系统', () => {
|
|
63
|
-
it('应该能导入 ReputationManager 和等级配置', () => {
|
|
64
|
-
expect(ReputationManager).toBeDefined();
|
|
65
|
-
expect(REPUTATION_TIERS).toBeDefined();
|
|
66
|
-
expect(REPUTATION_TIERS.length).toBe(5); // 5个等级
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('REPUTATION_TIERS 应该包含完整的业务等级定义', () => {
|
|
70
|
-
const levels: ReputationLevel[] = ['restricted', 'novice', 'participant', 'contributor', 'core'];
|
|
71
|
-
REPUTATION_TIERS.forEach((tier, index) => {
|
|
72
|
-
expect(tier.level).toBe(levels[index]);
|
|
73
|
-
expect(tier.permissions).toHaveProperty('canPublish');
|
|
74
|
-
expect(tier.permissions).toHaveProperty('canExecute');
|
|
75
|
-
expect(tier.permissions).toHaveProperty('canReview');
|
|
76
|
-
expect(tier.permissions).toHaveProperty('publishPriority');
|
|
77
|
-
expect(tier.permissions).toHaveProperty('publishDiscount');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('应该能导入 ReviewCommittee 进行任务评审', () => {
|
|
82
|
-
expect(ReviewCommittee).toBeDefined();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('应该能导入 AutonomousEconomy 进行经济系统管理', () => {
|
|
86
|
-
expect(AutonomousEconomy).toBeDefined();
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('场景 1.4: 用户配置网络安全', () => {
|
|
91
|
-
it('应该能导入 ChainSignatureManager 进行链式签名', () => {
|
|
92
|
-
expect(ChainSignatureManager).toBeDefined();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('应该能导入 InvitationManager 管理节点邀请', () => {
|
|
96
|
-
expect(InvitationManager).toBeDefined();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('应该能导入 ChallengeManager 进行节点挑战验证', () => {
|
|
100
|
-
expect(ChallengeManager).toBeDefined();
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('场景 1.5: 用户配置基础设施', () => {
|
|
105
|
-
it('应该能导入 Logger 进行日志记录', () => {
|
|
106
|
-
expect(Logger).toBeDefined();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('应该能导入 RateLimiter 进行速率限制', () => {
|
|
110
|
-
expect(RateLimiter).toBeDefined();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('应该能导入中间件工厂函数', () => {
|
|
114
|
-
expect(createMessageSizeLimitMiddleware).toBeDefined();
|
|
115
|
-
expect(createMessageTypeFilterMiddleware).toBeDefined();
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// ============================================================================
|
|
121
|
-
// 业务场景 2: 真实业务功能验证
|
|
122
|
-
// 测试模块在真实业务场景中的工作能力
|
|
123
|
-
// ============================================================================
|
|
124
|
-
describe('SDK Integration - 真实业务场景', () => {
|
|
125
|
-
describe('场景 2.1: 信誉管理业务流程', () => {
|
|
126
|
-
it('应该能获取新节点的初始信誉分(默认70分)', () => {
|
|
127
|
-
const reputationManager = new ReputationManager();
|
|
128
|
-
const peerId = 'peer-' + Date.now() + '-' + Math.random();
|
|
129
|
-
const initialRep = reputationManager.getReputation(peerId);
|
|
130
|
-
expect(initialRep.score).toBe(70); // 默认初始分是70
|
|
131
|
-
expect(initialRep.level).toBe('contributor'); // 70分对应contributor等级
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('应该能记录任务成功并改变信誉分', () => {
|
|
135
|
-
// 使用自定义配置创建新的管理器,避免状态污染
|
|
136
|
-
const reputationManager = new ReputationManager({
|
|
137
|
-
initialScore: 50,
|
|
138
|
-
alpha: 0.3,
|
|
139
|
-
minScore: 0,
|
|
140
|
-
maxScore: 100,
|
|
141
|
-
maxHistory: 100,
|
|
142
|
-
});
|
|
143
|
-
const peerId = 'peer-' + randomUUID();
|
|
144
|
-
|
|
145
|
-
// 先获取初始状态(会创建条目,分数为50)
|
|
146
|
-
const initialRep = reputationManager.getReputation(peerId);
|
|
147
|
-
expect(initialRep.score).toBe(50);
|
|
148
|
-
// 记录初始历史长度(getReputation 会创建 'initial' 记录)
|
|
149
|
-
const initialHistoryLength = initialRep.history.length;
|
|
150
|
-
|
|
151
|
-
// 记录成功,delta=10
|
|
152
|
-
reputationManager.recordSuccess(peerId, 'task-1', 10);
|
|
153
|
-
const updatedRep = reputationManager.getReputation(peerId);
|
|
154
|
-
|
|
155
|
-
// EWMA 算法: newScore = 0.3 * (50 + 10) + 0.7 * 50 = 53
|
|
156
|
-
// 验证分数确实增加了
|
|
157
|
-
expect(updatedRep.score).toBe(53);
|
|
158
|
-
expect(updatedRep.score).toBeGreaterThan(50);
|
|
159
|
-
|
|
160
|
-
// 验证历史记录增加了
|
|
161
|
-
expect(updatedRep.history.length).toBe(initialHistoryLength + 1);
|
|
162
|
-
expect(updatedRep.history[updatedRep.history.length - 1].type).toBe('task_success');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('应该能根据信誉分判断权限', () => {
|
|
166
|
-
const reputationManager = new ReputationManager();
|
|
167
|
-
const peerId = 'peer-' + Date.now() + '-' + Math.random();
|
|
168
|
-
// 默认70分为 contributor 等级,拥有所有权限
|
|
169
|
-
expect(reputationManager.hasPermission(peerId, 'publish')).toBe(true);
|
|
170
|
-
expect(reputationManager.hasPermission(peerId, 'execute')).toBe(true);
|
|
171
|
-
expect(reputationManager.hasPermission(peerId, 'review')).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('应该能获取信誉等级', () => {
|
|
175
|
-
const reputationManager = new ReputationManager();
|
|
176
|
-
const tier = reputationManager.getTier(75);
|
|
177
|
-
expect(tier.level).toBe('contributor');
|
|
178
|
-
expect(tier.permissions.canPublish).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('应该能记录任务失败并改变信誉分', () => {
|
|
182
|
-
// 使用自定义配置创建新的管理器,避免状态污染
|
|
183
|
-
const reputationManager = new ReputationManager({
|
|
184
|
-
initialScore: 50,
|
|
185
|
-
alpha: 0.3,
|
|
186
|
-
minScore: 0,
|
|
187
|
-
maxScore: 100,
|
|
188
|
-
maxHistory: 100,
|
|
189
|
-
});
|
|
190
|
-
const peerId = 'peer-' + randomUUID();
|
|
191
|
-
|
|
192
|
-
// 使用默认的 delta (-20) 或传入负数
|
|
193
|
-
reputationManager.recordFailure(peerId, 'task-fail', 'timeout');
|
|
194
|
-
const updatedRep = reputationManager.getReputation(peerId);
|
|
195
|
-
// 默认 delta 为 -20,EWMA: newScore = 0.3 * (50 - 20) + 0.7 * 50 = 44
|
|
196
|
-
expect(updatedRep.score).toBeLessThan(50);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('应该能获取发布优先级和折扣(根据实际等级)', () => {
|
|
200
|
-
const reputationManager = new ReputationManager();
|
|
201
|
-
const peerId = 'peer-' + Date.now() + '-' + Math.random();
|
|
202
|
-
// 默认70分为 contributor 等级,优先级为3,折扣90%
|
|
203
|
-
expect(reputationManager.getPublishPriority(peerId)).toBe(3);
|
|
204
|
-
expect(reputationManager.getPublishDiscount(peerId)).toBe(0.9);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('应该能获取高信誉节点列表', () => {
|
|
208
|
-
const reputationManager = new ReputationManager();
|
|
209
|
-
const peerA = 'peer-a-' + Date.now() + '-' + Math.random();
|
|
210
|
-
const peerB = 'peer-b-' + Date.now() + '-' + Math.random();
|
|
211
|
-
|
|
212
|
-
// 提升 peerA 的信誉
|
|
213
|
-
reputationManager.recordSuccess(peerA, 'task-1', 20);
|
|
214
|
-
reputationManager.recordSuccess(peerA, 'task-2', 20);
|
|
215
|
-
|
|
216
|
-
// peerB 保持默认
|
|
217
|
-
|
|
218
|
-
const highRepNodes = reputationManager.getHighReputationNodes(80);
|
|
219
|
-
const highRepPeerIds = highRepNodes.map(n => n.peerId);
|
|
220
|
-
expect(highRepPeerIds).toContain(peerA);
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe('场景 2.2: 速率限制业务流程', () => {
|
|
225
|
-
let rateLimiter: RateLimiter;
|
|
226
|
-
|
|
227
|
-
beforeEach(() => {
|
|
228
|
-
rateLimiter = new RateLimiter({ windowMs: 1000, maxRequests: 3 });
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
afterEach(() => {
|
|
232
|
-
rateLimiter.stop();
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it('应该允许在限制内的请求', () => {
|
|
236
|
-
const clientId = 'client-' + Date.now() + '-' + Math.random();
|
|
237
|
-
expect(rateLimiter.allowRequest(clientId)).toBe(true);
|
|
238
|
-
expect(rateLimiter.allowRequest(clientId)).toBe(true);
|
|
239
|
-
expect(rateLimiter.allowRequest(clientId)).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('应该拒绝超出限制的请求', () => {
|
|
243
|
-
const clientId = 'client-' + Date.now() + '-' + Math.random();
|
|
244
|
-
rateLimiter.allowRequest(clientId);
|
|
245
|
-
rateLimiter.allowRequest(clientId);
|
|
246
|
-
rateLimiter.allowRequest(clientId);
|
|
247
|
-
expect(rateLimiter.allowRequest(clientId)).toBe(false);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('应该能查询剩余令牌数', () => {
|
|
251
|
-
const clientId = 'client-' + Date.now() + '-' + Math.random();
|
|
252
|
-
expect(rateLimiter.getRemainingTokens(clientId)).toBe(3);
|
|
253
|
-
rateLimiter.allowRequest(clientId);
|
|
254
|
-
expect(rateLimiter.getRemainingTokens(clientId)).toBe(2);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('应该能重置限制', () => {
|
|
258
|
-
const clientId = 'client-' + Date.now() + '-' + Math.random();
|
|
259
|
-
rateLimiter.allowRequest(clientId);
|
|
260
|
-
rateLimiter.allowRequest(clientId);
|
|
261
|
-
rateLimiter.allowRequest(clientId);
|
|
262
|
-
expect(rateLimiter.allowRequest(clientId)).toBe(false);
|
|
263
|
-
|
|
264
|
-
rateLimiter.reset(clientId);
|
|
265
|
-
expect(rateLimiter.allowRequest(clientId)).toBe(true);
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe('场景 2.3: 请求签名业务流程', () => {
|
|
270
|
-
it('应该能签名和验证请求', () => {
|
|
271
|
-
const signer = new RequestSigner({ secretKey: 'my-secret-key' });
|
|
272
|
-
const payload = JSON.stringify({ action: 'publish', data: 'test' });
|
|
273
|
-
|
|
274
|
-
const signedMessage = signer.sign(payload);
|
|
275
|
-
expect(signedMessage).toBeDefined();
|
|
276
|
-
expect(signedMessage.signature).toBeDefined();
|
|
277
|
-
expect(signedMessage.timestamp).toBeDefined();
|
|
278
|
-
expect(signedMessage.nonce).toBeDefined();
|
|
279
|
-
|
|
280
|
-
const result = signer.verify(signedMessage);
|
|
281
|
-
expect(result.valid).toBe(true);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it('应该拒绝篡改后的请求', () => {
|
|
285
|
-
const signer = new RequestSigner({ secretKey: 'my-secret-key' });
|
|
286
|
-
const payload = JSON.stringify({ action: 'publish', data: 'test' });
|
|
287
|
-
const signedMessage = signer.sign(payload);
|
|
288
|
-
|
|
289
|
-
// 篡改 payload
|
|
290
|
-
signedMessage.payload = JSON.stringify({ action: 'publish', data: 'tampered' });
|
|
291
|
-
const result = signer.verify(signedMessage);
|
|
292
|
-
expect(result.valid).toBe(false);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
describe('场景 2.4: 日志记录业务流程', () => {
|
|
297
|
-
it('应该能创建带组件标识的日志记录器', () => {
|
|
298
|
-
const logger = new Logger({ component: 'TestService' });
|
|
299
|
-
expect(logger).toBeInstanceOf(Logger);
|
|
300
|
-
|
|
301
|
-
// 验证日志方法存在且可调用
|
|
302
|
-
expect(() => logger.info('test message')).not.toThrow();
|
|
303
|
-
expect(() => logger.error('error message')).not.toThrow();
|
|
304
|
-
expect(() => logger.warn('warning message')).not.toThrow();
|
|
305
|
-
expect(() => logger.debug('debug message')).not.toThrow();
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
describe('场景 2.5: 中间件业务流程', () => {
|
|
310
|
-
it('消息大小限制中间件应该能拦截大消息', async () => {
|
|
311
|
-
const middleware = createMessageSizeLimitMiddleware(100);
|
|
312
|
-
const context: MiddlewareContext = {
|
|
313
|
-
message: {
|
|
314
|
-
id: 'msg-1',
|
|
315
|
-
type: 'task',
|
|
316
|
-
payload: { data: 'x'.repeat(200) }, // 大消息
|
|
317
|
-
timestamp: Date.now(),
|
|
318
|
-
},
|
|
319
|
-
peerId: 'peer-1',
|
|
320
|
-
metadata: new Map(),
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
const result = await middleware.process(context);
|
|
324
|
-
expect(result.action).toBe('drop');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('消息类型过滤中间件应该能过滤不允许的类型', async () => {
|
|
328
|
-
const middleware = createMessageTypeFilterMiddleware(['task', 'response']);
|
|
329
|
-
const context: MiddlewareContext = {
|
|
330
|
-
message: {
|
|
331
|
-
id: 'msg-2',
|
|
332
|
-
type: 'unknown-type',
|
|
333
|
-
payload: {},
|
|
334
|
-
timestamp: Date.now(),
|
|
335
|
-
},
|
|
336
|
-
peerId: 'peer-1',
|
|
337
|
-
metadata: new Map(),
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const result = await middleware.process(context);
|
|
341
|
-
expect(result.action).toBe('drop');
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('允许的消息应该继续处理', async () => {
|
|
345
|
-
const middleware = createMessageTypeFilterMiddleware(['task', 'response']);
|
|
346
|
-
const context: MiddlewareContext = {
|
|
347
|
-
message: {
|
|
348
|
-
id: 'msg-3',
|
|
349
|
-
type: 'task',
|
|
350
|
-
payload: {},
|
|
351
|
-
timestamp: Date.now(),
|
|
352
|
-
},
|
|
353
|
-
peerId: 'peer-1',
|
|
354
|
-
metadata: new Map(),
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
const result = await middleware.process(context);
|
|
358
|
-
expect(result.action).toBe('continue');
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
describe('场景 2.6: 评审委员会业务流程', () => {
|
|
363
|
-
it('应该能创建评审委员会', () => {
|
|
364
|
-
const reputationManager = new ReputationManager();
|
|
365
|
-
const committee = new ReviewCommittee({
|
|
366
|
-
reputationManager,
|
|
367
|
-
minReviewers: 2,
|
|
368
|
-
maxReviewers: 5,
|
|
369
|
-
reviewTimeout: 5000,
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
expect(committee).toBeInstanceOf(ReviewCommittee);
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
describe('场景 2.7: 自治经济系统业务流程', () => {
|
|
377
|
-
it('应该能创建经济系统', () => {
|
|
378
|
-
const reputationManager = new ReputationManager();
|
|
379
|
-
const economy = new AutonomousEconomy({
|
|
380
|
-
reputationManager,
|
|
381
|
-
baseTaskCost: 10,
|
|
382
|
-
complexityMultiplier: 1.5,
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
expect(economy).toBeInstanceOf(AutonomousEconomy);
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
describe('场景 2.8: 邀请管理业务流程', () => {
|
|
390
|
-
it('应该能创建邀请管理器', () => {
|
|
391
|
-
const reputationManager = new ReputationManager();
|
|
392
|
-
const invitationManager = new InvitationManager({
|
|
393
|
-
reputationManager,
|
|
394
|
-
minInviterReputation: 60,
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
expect(invitationManager).toBeInstanceOf(InvitationManager);
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
describe('场景 2.9: 挑战管理业务流程', () => {
|
|
402
|
-
it('应该能创建挑战管理器', () => {
|
|
403
|
-
const reputationManager = new ReputationManager();
|
|
404
|
-
const challengeManager = new ChallengeManager({
|
|
405
|
-
reputationManager,
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
expect(challengeManager).toBeInstanceOf(ChallengeManager);
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
describe('场景 2.10: 链式签名业务流程', () => {
|
|
413
|
-
it('应该能创建链式签名管理器', () => {
|
|
414
|
-
const signatureManager = new ChainSignatureManager();
|
|
415
|
-
expect(signatureManager).toBeInstanceOf(ChainSignatureManager);
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
// ============================================================================
|
|
421
|
-
// 业务场景 3: 版本和兼容性
|
|
422
|
-
// ============================================================================
|
|
423
|
-
describe('SDK Version - 版本管理', () => {
|
|
424
|
-
it('应该导出有效的语义化版本号', () => {
|
|
425
|
-
expect(VERSION).toBeDefined();
|
|
426
|
-
expect(typeof VERSION).toBe('string');
|
|
427
|
-
expect(VERSION).toMatch(/^\d+\.\d+\.\d+$/);
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
it('版本号应该符合当前发布版本', () => {
|
|
431
|
-
const [major, minor, patch] = VERSION.split('.').map(Number);
|
|
432
|
-
expect(major).toBeGreaterThanOrEqual(1);
|
|
433
|
-
expect(minor).toBeGreaterThanOrEqual(0);
|
|
434
|
-
expect(patch).toBeGreaterThanOrEqual(0);
|
|
435
|
-
});
|
|
436
|
-
});
|