agentvault 1.0.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/.dfx/local/network-id +4 -0
- package/.next/trace +2 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/AGENTS.md +43 -0
- package/CHANGELOG.md +196 -0
- package/LICENSE +21 -0
- package/PLAN_VAULT_INTEGRATION.md +318 -0
- package/README.md +253 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-28-967Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-29-032Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-373Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-428Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-132Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-247Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-216Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-283Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-772Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-793Z.json +28 -0
- package/backups/test-backup.json +28 -0
- package/dist/cli/commands/approve.d.ts +4 -0
- package/dist/cli/commands/approve.js +232 -0
- package/dist/cli/commands/archive.d.ts +4 -0
- package/dist/cli/commands/archive.js +192 -0
- package/dist/cli/commands/backup.d.ts +4 -0
- package/dist/cli/commands/backup.js +164 -0
- package/dist/cli/commands/cloud-backup.d.ts +4 -0
- package/dist/cli/commands/cloud-backup.js +221 -0
- package/dist/cli/commands/cycles.d.ts +8 -0
- package/dist/cli/commands/cycles.js +83 -0
- package/dist/cli/commands/decrypt.d.ts +16 -0
- package/dist/cli/commands/decrypt.js +101 -0
- package/dist/cli/commands/deploy.d.ts +32 -0
- package/dist/cli/commands/deploy.js +208 -0
- package/dist/cli/commands/exec.d.ts +26 -0
- package/dist/cli/commands/exec.js +109 -0
- package/dist/cli/commands/fetch.d.ts +23 -0
- package/dist/cli/commands/fetch.js +164 -0
- package/dist/cli/commands/health.d.ts +8 -0
- package/dist/cli/commands/health.js +72 -0
- package/dist/cli/commands/identity.d.ts +8 -0
- package/dist/cli/commands/identity.js +140 -0
- package/dist/cli/commands/inference.d.ts +4 -0
- package/dist/cli/commands/inference.js +225 -0
- package/dist/cli/commands/info.d.ts +8 -0
- package/dist/cli/commands/info.js +59 -0
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.js +135 -0
- package/dist/cli/commands/instrument.d.ts +8 -0
- package/dist/cli/commands/instrument.js +35 -0
- package/dist/cli/commands/list.d.ts +36 -0
- package/dist/cli/commands/list.js +173 -0
- package/dist/cli/commands/logs.d.ts +8 -0
- package/dist/cli/commands/logs.js +96 -0
- package/dist/cli/commands/monitor.d.ts +8 -0
- package/dist/cli/commands/monitor.js +84 -0
- package/dist/cli/commands/network.d.ts +14 -0
- package/dist/cli/commands/network.js +258 -0
- package/dist/cli/commands/package.d.ts +36 -0
- package/dist/cli/commands/package.js +188 -0
- package/dist/cli/commands/profile.d.ts +8 -0
- package/dist/cli/commands/profile.js +76 -0
- package/dist/cli/commands/promote.d.ts +8 -0
- package/dist/cli/commands/promote.js +89 -0
- package/dist/cli/commands/rebuild.d.ts +21 -0
- package/dist/cli/commands/rebuild.js +140 -0
- package/dist/cli/commands/rollback.d.ts +8 -0
- package/dist/cli/commands/rollback.js +120 -0
- package/dist/cli/commands/show.d.ts +36 -0
- package/dist/cli/commands/show.js +200 -0
- package/dist/cli/commands/stats.d.ts +8 -0
- package/dist/cli/commands/stats.js +34 -0
- package/dist/cli/commands/status.d.ts +14 -0
- package/dist/cli/commands/status.js +83 -0
- package/dist/cli/commands/test.d.ts +8 -0
- package/dist/cli/commands/test.js +109 -0
- package/dist/cli/commands/tokens.d.ts +8 -0
- package/dist/cli/commands/tokens.js +62 -0
- package/dist/cli/commands/trace.d.ts +8 -0
- package/dist/cli/commands/trace.js +68 -0
- package/dist/cli/commands/wallet-export.d.ts +13 -0
- package/dist/cli/commands/wallet-export.js +140 -0
- package/dist/cli/commands/wallet-history.d.ts +10 -0
- package/dist/cli/commands/wallet-history.js +127 -0
- package/dist/cli/commands/wallet-import.d.ts +10 -0
- package/dist/cli/commands/wallet-import.js +209 -0
- package/dist/cli/commands/wallet-multi-send.d.ts +17 -0
- package/dist/cli/commands/wallet-multi-send.js +195 -0
- package/dist/cli/commands/wallet-process-queue.d.ts +19 -0
- package/dist/cli/commands/wallet-process-queue.js +209 -0
- package/dist/cli/commands/wallet-sign.d.ts +13 -0
- package/dist/cli/commands/wallet-sign.js +207 -0
- package/dist/cli/commands/wallet.d.ts +12 -0
- package/dist/cli/commands/wallet.js +794 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +96 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.js +14 -0
- package/fixup_1_0_OSS_release.md +136 -0
- package/fixup_REALEASE_PRD.md +136 -0
- package/package.json +79 -0
- package/pnpm-workspace.yaml +5 -0
- package/scripts/dev-dashboard.mjs +84 -0
- package/site/README.md +63 -0
- package/site/docusaurus.config.ts +148 -0
- package/site/package-lock.json +18383 -0
- package/site/package.json +47 -0
- package/site/sidebars.ts +86 -0
- package/site/static/.gitkeep +0 -0
- package/site/static/img/logo.svg +28 -0
- package/site/static/img/og-image.svg +35 -0
- package/src/archival/archive-manager.ts +372 -0
- package/src/archival/arweave-client.ts +289 -0
- package/src/archival/index.ts +8 -0
- package/src/backup/backup.ts +315 -0
- package/src/backup/index.ts +7 -0
- package/src/cloud-storage/cloud-sync.ts +461 -0
- package/src/cloud-storage/index.ts +11 -0
- package/src/cloud-storage/provider-detector.ts +198 -0
- package/src/cloud-storage/types.ts +104 -0
- package/src/debugging/index.ts +6 -0
- package/src/debugging/logs.ts +193 -0
- package/src/debugging/types.ts +100 -0
- package/src/deployment/deployer.ts +274 -0
- package/src/deployment/icpClient.ts +620 -0
- package/src/deployment/index.ts +46 -0
- package/src/deployment/promotion.ts +161 -0
- package/src/deployment/types.ts +111 -0
- package/src/icp/batch.ts +374 -0
- package/src/icp/cycles.ts +50 -0
- package/src/icp/environment.ts +215 -0
- package/src/icp/icpcli.ts +438 -0
- package/src/icp/icwasm.ts +222 -0
- package/src/icp/identity.ts +77 -0
- package/src/icp/index.ts +94 -0
- package/src/icp/optimization.ts +242 -0
- package/src/icp/tokens.ts +36 -0
- package/src/icp/tool-detector.ts +110 -0
- package/src/icp/types.ts +574 -0
- package/src/index.ts +25 -0
- package/src/inference/bittensor-client.ts +304 -0
- package/src/inference/index.ts +8 -0
- package/src/inference/inference-manager.ts +327 -0
- package/src/metrics/index.ts +7 -0
- package/src/metrics/metrics.ts +186 -0
- package/src/monitoring/alerting.ts +190 -0
- package/src/monitoring/health.ts +197 -0
- package/src/monitoring/index.ts +38 -0
- package/src/monitoring/info.ts +114 -0
- package/src/monitoring/types.ts +99 -0
- package/src/network/index.ts +5 -0
- package/src/network/network-config.ts +129 -0
- package/src/packaging/compiler.ts +647 -0
- package/src/packaging/config-persistence.ts +135 -0
- package/src/packaging/config-schemas.ts +156 -0
- package/src/packaging/detector.ts +220 -0
- package/src/packaging/index.ts +90 -0
- package/src/packaging/packager.ts +118 -0
- package/src/packaging/parsers/clawdbot.ts +278 -0
- package/src/packaging/parsers/cline.ts +223 -0
- package/src/packaging/parsers/generic.ts +266 -0
- package/src/packaging/parsers/goose.ts +214 -0
- package/src/packaging/parsers/index.ts +11 -0
- package/src/packaging/serializer.ts +260 -0
- package/src/packaging/types.ts +144 -0
- package/src/packaging/wasmedge-compiler.ts +406 -0
- package/src/security/index.ts +17 -0
- package/src/security/multisig.ts +415 -0
- package/src/security/types.ts +416 -0
- package/src/security/vetkeys.ts +655 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/local-runner.ts +264 -0
- package/src/testing/types.ts +104 -0
- package/src/wallet/cbor-serializer.ts +323 -0
- package/src/wallet/chain-dispatcher.ts +313 -0
- package/src/wallet/cross-chain-aggregator.ts +346 -0
- package/src/wallet/index.ts +76 -0
- package/src/wallet/key-derivation.ts +425 -0
- package/src/wallet/providers/base-provider.ts +154 -0
- package/src/wallet/providers/cketh-provider.ts +434 -0
- package/src/wallet/providers/polkadot-provider.ts +503 -0
- package/src/wallet/providers/solana-provider.ts +490 -0
- package/src/wallet/transaction-queue.ts +284 -0
- package/src/wallet/types.ts +178 -0
- package/src/wallet/vetkeys-adapter.ts +431 -0
- package/src/wallet/wallet-manager.ts +597 -0
- package/src/wallet/wallet-storage.ts +380 -0
- package/vercel.json +8 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local test runner for canister testing
|
|
3
|
+
*
|
|
4
|
+
* Provides test execution for unit, integration, and load tests against local canisters
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execa } from 'execa';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import type {
|
|
12
|
+
TestRunnerOptions,
|
|
13
|
+
TestSuite,
|
|
14
|
+
LoadTestConfig,
|
|
15
|
+
LoadTestResult,
|
|
16
|
+
} from './types.js';
|
|
17
|
+
|
|
18
|
+
const AGENTVAULT_DIR = path.join(os.homedir(), '.agentvault');
|
|
19
|
+
const TEST_RESULTS_DIR = path.join(AGENTVAULT_DIR, 'test-results');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ensure test results directory exists
|
|
23
|
+
*/
|
|
24
|
+
function ensureTestResultsDir(): void {
|
|
25
|
+
if (!fs.existsSync(AGENTVAULT_DIR)) {
|
|
26
|
+
fs.mkdirSync(AGENTVAULT_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
if (!fs.existsSync(TEST_RESULTS_DIR)) {
|
|
29
|
+
fs.mkdirSync(TEST_RESULTS_DIR, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get test results file path
|
|
35
|
+
*/
|
|
36
|
+
function getTestResultsPath(agentName: string, testType: string): string {
|
|
37
|
+
ensureTestResultsDir();
|
|
38
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
39
|
+
return path.join(TEST_RESULTS_DIR, `${agentName}-${testType}-${timestamp}.json`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse vitest output to extract test results
|
|
44
|
+
*/
|
|
45
|
+
function parseVitestOutput(stdout: string): TestSuite {
|
|
46
|
+
const lines = stdout.split('\n');
|
|
47
|
+
const tests: TestSuite['tests'] = [];
|
|
48
|
+
let total = 0;
|
|
49
|
+
let passed = 0;
|
|
50
|
+
let failed = 0;
|
|
51
|
+
let skipped = 0;
|
|
52
|
+
let duration = 0;
|
|
53
|
+
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const match = line.match(/^(PASS|FAIL|SKIP)\s+(.+) \((\d+)ms\)$/);
|
|
56
|
+
if (match) {
|
|
57
|
+
const [, status, nameRaw, timeRaw] = match;
|
|
58
|
+
const name = nameRaw?.trim() || 'unknown';
|
|
59
|
+
const time = timeRaw || '0';
|
|
60
|
+
tests.push({
|
|
61
|
+
name,
|
|
62
|
+
status: status === 'PASS' ? 'passed' : status === 'FAIL' ? 'failed' : 'skipped',
|
|
63
|
+
duration: parseInt(time, 10),
|
|
64
|
+
});
|
|
65
|
+
total++;
|
|
66
|
+
if (status === 'PASS') passed++;
|
|
67
|
+
else if (status === 'FAIL') failed++;
|
|
68
|
+
else skipped++;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const durationMatch = line.match(/Test Files\s+(\d+)\s+passed\s+\((\d+)\)/);
|
|
72
|
+
if (durationMatch && durationMatch[2]) {
|
|
73
|
+
duration = parseInt(durationMatch[2], 10);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
name: 'canister-tests',
|
|
79
|
+
total,
|
|
80
|
+
passed,
|
|
81
|
+
failed,
|
|
82
|
+
skipped,
|
|
83
|
+
duration,
|
|
84
|
+
tests,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Run unit tests
|
|
90
|
+
*/
|
|
91
|
+
export async function runUnitTests(options: TestRunnerOptions): Promise<TestSuite> {
|
|
92
|
+
const vitestArgs = [
|
|
93
|
+
'run',
|
|
94
|
+
'--reporter=verbose',
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
if (options.network) {
|
|
98
|
+
vitestArgs.push(`--env.network=${options.network}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (options.outputFormat === 'json') {
|
|
102
|
+
vitestArgs.push('--reporter=json');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
vitestArgs.push('tests/unit');
|
|
106
|
+
|
|
107
|
+
const result = await execa('npx', ['vitest', ...vitestArgs], {
|
|
108
|
+
reject: false,
|
|
109
|
+
timeout: 120_000,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const testSuite = parseVitestOutput(result.stdout);
|
|
113
|
+
|
|
114
|
+
if (options.outputFormat === 'json') {
|
|
115
|
+
const resultsPath = getTestResultsPath(options.agentName, 'unit');
|
|
116
|
+
fs.writeFileSync(resultsPath, JSON.stringify(testSuite, null, 2), 'utf8');
|
|
117
|
+
console.log(`Test results saved to ${resultsPath}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return testSuite;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Run integration tests
|
|
125
|
+
*/
|
|
126
|
+
export async function runIntegrationTests(options: TestRunnerOptions): Promise<TestSuite> {
|
|
127
|
+
const vitestArgs = [
|
|
128
|
+
'run',
|
|
129
|
+
'--reporter=verbose',
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
if (options.network) {
|
|
133
|
+
vitestArgs.push(`--env.network=${options.network}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.outputFormat === 'json') {
|
|
137
|
+
vitestArgs.push('--reporter=json');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
vitestArgs.push('tests/integration');
|
|
141
|
+
|
|
142
|
+
const result = await execa('npx', ['vitest', ...vitestArgs], {
|
|
143
|
+
reject: false,
|
|
144
|
+
timeout: 300_000,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const testSuite = parseVitestOutput(result.stdout);
|
|
148
|
+
|
|
149
|
+
if (options.outputFormat === 'json') {
|
|
150
|
+
const resultsPath = getTestResultsPath(options.agentName, 'integration');
|
|
151
|
+
fs.writeFileSync(resultsPath, JSON.stringify(testSuite, null, 2), 'utf8');
|
|
152
|
+
console.log(`Test results saved to ${resultsPath}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return testSuite;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Run load tests
|
|
160
|
+
*/
|
|
161
|
+
export async function runLoadTests(config: LoadTestConfig): Promise<LoadTestResult> {
|
|
162
|
+
const errors: Record<string, number> = {};
|
|
163
|
+
let totalRequests = 0;
|
|
164
|
+
let successfulRequests = 0;
|
|
165
|
+
let failedRequests = 0;
|
|
166
|
+
const responseTimes: number[] = [];
|
|
167
|
+
const startTime = Date.now();
|
|
168
|
+
const endTime = startTime + (config.duration * 1000);
|
|
169
|
+
const interval = config.duration * 1000 / (config.concurrency * 10);
|
|
170
|
+
|
|
171
|
+
const requestPromises: Array<Promise<{ success: boolean; responseTime: number; error?: string }>> = [];
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < config.concurrency * 10; i++) {
|
|
174
|
+
if (Date.now() >= endTime) break;
|
|
175
|
+
|
|
176
|
+
const startTimeMs = Date.now();
|
|
177
|
+
|
|
178
|
+
const request = execa('npx', ['icp', 'canister', 'call', config.canisterId, config.method, ...(config.args ? [config.args] : [])], {
|
|
179
|
+
reject: false,
|
|
180
|
+
timeout: 30_000,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const requestPromise = request.then((result) => {
|
|
184
|
+
const responseTime = Date.now() - startTimeMs;
|
|
185
|
+
if (result.exitCode === 0) {
|
|
186
|
+
return { success: true, responseTime };
|
|
187
|
+
} else {
|
|
188
|
+
const error = result.stderr || 'Unknown error';
|
|
189
|
+
errors[error] = (errors[error] || 0) + 1;
|
|
190
|
+
return { success: false, responseTime, error };
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
requestPromises.push(requestPromise);
|
|
195
|
+
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const results = await Promise.all(requestPromises);
|
|
200
|
+
|
|
201
|
+
for (const result of results) {
|
|
202
|
+
totalRequests++;
|
|
203
|
+
if (result.success) {
|
|
204
|
+
successfulRequests++;
|
|
205
|
+
responseTimes.push(result.responseTime);
|
|
206
|
+
} else {
|
|
207
|
+
failedRequests++;
|
|
208
|
+
if (result.error) {
|
|
209
|
+
errors[result.error] = (errors[result.error] || 0) + 1;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const actualDuration = (Date.now() - startTime) / 1000;
|
|
215
|
+
const requestsPerSecond = totalRequests / actualDuration;
|
|
216
|
+
|
|
217
|
+
const sortedTimes = responseTimes.sort((a, b) => a - b);
|
|
218
|
+
const minResponseTime = sortedTimes[0] || 0;
|
|
219
|
+
const maxResponseTime = sortedTimes[sortedTimes.length - 1] || 0;
|
|
220
|
+
const avgResponseTime = responseTimes.length > 0
|
|
221
|
+
? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length
|
|
222
|
+
: 0;
|
|
223
|
+
|
|
224
|
+
const percentiles = {
|
|
225
|
+
p50: sortedTimes[Math.floor(sortedTimes.length * 0.5)] || 0,
|
|
226
|
+
p90: sortedTimes[Math.floor(sortedTimes.length * 0.9)] || 0,
|
|
227
|
+
p95: sortedTimes[Math.floor(sortedTimes.length * 0.95)] || 0,
|
|
228
|
+
p99: sortedTimes[Math.floor(sortedTimes.length * 0.99)] || 0,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
totalRequests,
|
|
233
|
+
successfulRequests,
|
|
234
|
+
failedRequests,
|
|
235
|
+
requestsPerSecond,
|
|
236
|
+
avgResponseTime,
|
|
237
|
+
minResponseTime,
|
|
238
|
+
maxResponseTime,
|
|
239
|
+
percentiles,
|
|
240
|
+
errors,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Run tests based on options
|
|
246
|
+
*/
|
|
247
|
+
export async function runTests(options: TestRunnerOptions): Promise<TestSuite | LoadTestResult> {
|
|
248
|
+
switch (options.testType) {
|
|
249
|
+
case 'unit':
|
|
250
|
+
return runUnitTests(options);
|
|
251
|
+
case 'integration':
|
|
252
|
+
return runIntegrationTests(options);
|
|
253
|
+
case 'load-test':
|
|
254
|
+
if (!options.concurrency) {
|
|
255
|
+
throw new Error('Concurrency is required for load tests');
|
|
256
|
+
}
|
|
257
|
+
if (!options.loadDuration) {
|
|
258
|
+
throw new Error('Load duration is required for load tests');
|
|
259
|
+
}
|
|
260
|
+
throw new Error('Load test requires specific LoadTestConfig');
|
|
261
|
+
default:
|
|
262
|
+
throw new Error(`Unknown test type: ${options.testType}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for local test runner
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Test type */
|
|
6
|
+
export type TestType = 'unit' | 'integration' | 'load-test';
|
|
7
|
+
|
|
8
|
+
/** Test result status */
|
|
9
|
+
export type TestStatus = 'passed' | 'failed' | 'skipped';
|
|
10
|
+
|
|
11
|
+
/** Individual test case result */
|
|
12
|
+
export interface TestCase {
|
|
13
|
+
/** Test name */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Test status */
|
|
16
|
+
status: TestStatus;
|
|
17
|
+
/** Duration in milliseconds */
|
|
18
|
+
duration: number;
|
|
19
|
+
/** Error message if failed */
|
|
20
|
+
error?: string;
|
|
21
|
+
/** Error stack if failed */
|
|
22
|
+
stack?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Test suite result */
|
|
26
|
+
export interface TestSuite {
|
|
27
|
+
/** Suite name */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Total number of tests */
|
|
30
|
+
total: number;
|
|
31
|
+
/** Number of passed tests */
|
|
32
|
+
passed: number;
|
|
33
|
+
/** Number of failed tests */
|
|
34
|
+
failed: number;
|
|
35
|
+
/** Number of skipped tests */
|
|
36
|
+
skipped: number;
|
|
37
|
+
/** Duration in milliseconds */
|
|
38
|
+
duration: number;
|
|
39
|
+
/** Individual test cases */
|
|
40
|
+
tests: TestCase[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Test runner options */
|
|
44
|
+
export interface TestRunnerOptions {
|
|
45
|
+
/** Agent name to test */
|
|
46
|
+
agentName: string;
|
|
47
|
+
/** Network to run tests against */
|
|
48
|
+
network: string;
|
|
49
|
+
/** Test type */
|
|
50
|
+
testType: TestType;
|
|
51
|
+
/** Enable watch mode */
|
|
52
|
+
watch?: boolean;
|
|
53
|
+
/** Load test concurrency */
|
|
54
|
+
concurrency?: number;
|
|
55
|
+
/** Load test duration in seconds */
|
|
56
|
+
loadDuration?: number;
|
|
57
|
+
/** Output format */
|
|
58
|
+
outputFormat?: 'json' | 'junit' | 'html';
|
|
59
|
+
/** Verbose output */
|
|
60
|
+
verbose?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Load test configuration */
|
|
64
|
+
export interface LoadTestConfig {
|
|
65
|
+
/** Number of concurrent requests */
|
|
66
|
+
concurrency: number;
|
|
67
|
+
/** Test duration in seconds */
|
|
68
|
+
duration: number;
|
|
69
|
+
/** Target canister ID */
|
|
70
|
+
canisterId: string;
|
|
71
|
+
/** Method to call */
|
|
72
|
+
method: string;
|
|
73
|
+
/** Request arguments (Candid format) */
|
|
74
|
+
args?: string;
|
|
75
|
+
/** Ramp-up time in seconds */
|
|
76
|
+
rampUp?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Load test result */
|
|
80
|
+
export interface LoadTestResult {
|
|
81
|
+
/** Total requests sent */
|
|
82
|
+
totalRequests: number;
|
|
83
|
+
/** Successful requests */
|
|
84
|
+
successfulRequests: number;
|
|
85
|
+
/** Failed requests */
|
|
86
|
+
failedRequests: number;
|
|
87
|
+
/** Requests per second */
|
|
88
|
+
requestsPerSecond: number;
|
|
89
|
+
/** Average response time in milliseconds */
|
|
90
|
+
avgResponseTime: number;
|
|
91
|
+
/** Minimum response time in milliseconds */
|
|
92
|
+
minResponseTime: number;
|
|
93
|
+
/** Maximum response time in milliseconds */
|
|
94
|
+
maxResponseTime: number;
|
|
95
|
+
/** Percentiles */
|
|
96
|
+
percentiles: {
|
|
97
|
+
p50: number;
|
|
98
|
+
p90: number;
|
|
99
|
+
p95: number;
|
|
100
|
+
p99: number;
|
|
101
|
+
};
|
|
102
|
+
/** Error breakdown */
|
|
103
|
+
errors: Record<string, number>;
|
|
104
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Serializer/Deserializer
|
|
3
|
+
*
|
|
4
|
+
* Uses cbor-x to encode/decode wallet data and transactions.
|
|
5
|
+
* Provides efficient binary serialization for wallet operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as cbor from 'cbor-x';
|
|
9
|
+
import type {
|
|
10
|
+
WalletData,
|
|
11
|
+
Transaction,
|
|
12
|
+
TransactionRequest,
|
|
13
|
+
SignedTransaction,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* CBOR serializer options
|
|
18
|
+
*/
|
|
19
|
+
interface CborOptions {
|
|
20
|
+
/** Include diagnostic info */
|
|
21
|
+
diagnostic?: boolean;
|
|
22
|
+
/** Use indefinite-length encoding */
|
|
23
|
+
indefinite?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Serialize wallet data to CBOR
|
|
28
|
+
*
|
|
29
|
+
* @param wallet - Wallet data to serialize
|
|
30
|
+
* @param options - CBOR encoding options
|
|
31
|
+
* @returns CBOR-encoded wallet data
|
|
32
|
+
*/
|
|
33
|
+
export function serializeWallet(
|
|
34
|
+
wallet: WalletData,
|
|
35
|
+
_options: CborOptions = {}
|
|
36
|
+
): Uint8Array {
|
|
37
|
+
try {
|
|
38
|
+
const walletObject = {
|
|
39
|
+
id: wallet.id,
|
|
40
|
+
agentId: wallet.agentId,
|
|
41
|
+
chain: wallet.chain,
|
|
42
|
+
address: wallet.address,
|
|
43
|
+
privateKey: wallet.privateKey,
|
|
44
|
+
mnemonic: wallet.mnemonic,
|
|
45
|
+
seedDerivationPath: wallet.seedDerivationPath,
|
|
46
|
+
createdAt: wallet.createdAt,
|
|
47
|
+
updatedAt: wallet.updatedAt,
|
|
48
|
+
creationMethod: wallet.creationMethod,
|
|
49
|
+
chainMetadata: wallet.chainMetadata,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const encoded = cbor.encode(walletObject);
|
|
53
|
+
|
|
54
|
+
// Add checksum for tamper detection
|
|
55
|
+
const checksum = calculateChecksum(encoded);
|
|
56
|
+
const withChecksum = new Uint8Array(encoded.length + checksum.length);
|
|
57
|
+
withChecksum.set(encoded);
|
|
58
|
+
withChecksum.set(checksum, encoded.length);
|
|
59
|
+
|
|
60
|
+
return withChecksum;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
63
|
+
throw new Error(`Failed to serialize wallet: ${message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Deserialize CBOR data to wallet
|
|
69
|
+
*
|
|
70
|
+
* @param data - CBOR-encoded wallet data
|
|
71
|
+
* @returns Deserialized wallet data
|
|
72
|
+
*/
|
|
73
|
+
export function deserializeWallet(data: Uint8Array): WalletData {
|
|
74
|
+
try {
|
|
75
|
+
// Verify checksum
|
|
76
|
+
const checksumLength = 4;
|
|
77
|
+
if (data.length < checksumLength) {
|
|
78
|
+
throw new Error('Invalid wallet data: too short');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const payload = data.slice(0, -checksumLength);
|
|
82
|
+
const checksum = data.slice(-checksumLength);
|
|
83
|
+
const expectedChecksum = calculateChecksum(payload);
|
|
84
|
+
|
|
85
|
+
if (!buffersEqual(checksum, expectedChecksum)) {
|
|
86
|
+
throw new Error('Invalid wallet data: checksum mismatch');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const decoded = cbor.decode(payload) as any;
|
|
90
|
+
|
|
91
|
+
if (!decoded) {
|
|
92
|
+
throw new Error('Invalid wallet data: decode failed');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
id: decoded.id || '',
|
|
97
|
+
agentId: decoded.agentId || '',
|
|
98
|
+
chain: decoded.chain || 'cketh',
|
|
99
|
+
address: decoded.address || '',
|
|
100
|
+
privateKey: decoded.privateKey,
|
|
101
|
+
mnemonic: decoded.mnemonic,
|
|
102
|
+
seedDerivationPath: decoded.seedDerivationPath,
|
|
103
|
+
createdAt: decoded.createdAt || Date.now(),
|
|
104
|
+
updatedAt: decoded.updatedAt || Date.now(),
|
|
105
|
+
creationMethod: decoded.creationMethod || 'seed',
|
|
106
|
+
chainMetadata: decoded.chainMetadata,
|
|
107
|
+
} as WalletData;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
110
|
+
throw new Error(`Failed to deserialize wallet: ${message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Serialize transaction to CBOR
|
|
116
|
+
*
|
|
117
|
+
* @param transaction - Transaction to serialize
|
|
118
|
+
* @returns CBOR-encoded transaction
|
|
119
|
+
*/
|
|
120
|
+
export function serializeTransaction(transaction: Transaction): Uint8Array {
|
|
121
|
+
try {
|
|
122
|
+
const txObject = {
|
|
123
|
+
hash: transaction.hash,
|
|
124
|
+
from: transaction.from,
|
|
125
|
+
to: transaction.to,
|
|
126
|
+
amount: transaction.amount,
|
|
127
|
+
chain: transaction.chain,
|
|
128
|
+
timestamp: transaction.timestamp,
|
|
129
|
+
status: transaction.status,
|
|
130
|
+
fee: transaction.fee,
|
|
131
|
+
data: transaction.data,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return cbor.encode(txObject);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
137
|
+
throw new Error(`Failed to serialize transaction: ${message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Deserialize CBOR data to transaction
|
|
143
|
+
*
|
|
144
|
+
* @param data - CBOR-encoded transaction data
|
|
145
|
+
* @returns Deserialized transaction
|
|
146
|
+
*/
|
|
147
|
+
export function deserializeTransaction(data: Uint8Array): Transaction {
|
|
148
|
+
try {
|
|
149
|
+
const decoded = cbor.decode(data);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
hash: decoded.hash || '',
|
|
153
|
+
from: decoded.from || '',
|
|
154
|
+
to: decoded.to || '',
|
|
155
|
+
amount: decoded.amount || '0',
|
|
156
|
+
chain: decoded.chain || 'cketh',
|
|
157
|
+
timestamp: decoded.timestamp || Date.now(),
|
|
158
|
+
status: decoded.status || 'pending',
|
|
159
|
+
fee: decoded.fee,
|
|
160
|
+
data: decoded.data,
|
|
161
|
+
} as Transaction;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
164
|
+
throw new Error(`Failed to deserialize transaction: ${message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Serialize transaction request to CBOR
|
|
170
|
+
*
|
|
171
|
+
* @param request - Transaction request to serialize
|
|
172
|
+
* @returns CBOR-encoded transaction request
|
|
173
|
+
*/
|
|
174
|
+
export function serializeTransactionRequest(request: TransactionRequest): Uint8Array {
|
|
175
|
+
try {
|
|
176
|
+
const requestObject = {
|
|
177
|
+
to: request.to,
|
|
178
|
+
amount: request.amount,
|
|
179
|
+
chain: request.chain,
|
|
180
|
+
memo: request.memo,
|
|
181
|
+
gasPrice: request.gasPrice,
|
|
182
|
+
gasLimit: request.gasLimit,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return cbor.encode(requestObject);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
188
|
+
throw new Error(`Failed to serialize transaction request: ${message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Deserialize CBOR data to transaction request
|
|
194
|
+
*
|
|
195
|
+
* @param data - CBOR-encoded transaction request data
|
|
196
|
+
* @returns Deserialized transaction request
|
|
197
|
+
*/
|
|
198
|
+
export function deserializeTransactionRequest(data: Uint8Array): TransactionRequest {
|
|
199
|
+
try {
|
|
200
|
+
const decoded = cbor.decode(data);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
to: decoded.to || '',
|
|
204
|
+
amount: decoded.amount || '0',
|
|
205
|
+
chain: decoded.chain || 'cketh',
|
|
206
|
+
memo: decoded.memo,
|
|
207
|
+
gasPrice: decoded.gasPrice,
|
|
208
|
+
gasLimit: decoded.gasLimit,
|
|
209
|
+
} as TransactionRequest;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
212
|
+
throw new Error(`Failed to deserialize transaction request: ${message}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Serialize signed transaction to CBOR
|
|
218
|
+
*
|
|
219
|
+
* @param signedTx - Signed transaction to serialize
|
|
220
|
+
* @returns CBOR-encoded signed transaction
|
|
221
|
+
*/
|
|
222
|
+
export function serializeSignedTransaction(signedTx: SignedTransaction): Uint8Array {
|
|
223
|
+
try {
|
|
224
|
+
const txObject = {
|
|
225
|
+
txHash: signedTx.txHash,
|
|
226
|
+
signedTx: signedTx.signedTx,
|
|
227
|
+
signature: signedTx.signature,
|
|
228
|
+
request: signedTx.request,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return cbor.encode(txObject);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
234
|
+
throw new Error(`Failed to serialize signed transaction: ${message}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Deserialize CBOR data to signed transaction
|
|
240
|
+
*
|
|
241
|
+
* @param data - CBOR-encoded signed transaction data
|
|
242
|
+
* @returns Deserialized signed transaction
|
|
243
|
+
*/
|
|
244
|
+
export function deserializeSignedTransaction(data: Uint8Array): SignedTransaction {
|
|
245
|
+
try {
|
|
246
|
+
const decoded = cbor.decode(data);
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
txHash: decoded.txHash || '',
|
|
250
|
+
signedTx: decoded.signedTx || '',
|
|
251
|
+
signature: decoded.signature,
|
|
252
|
+
request: decoded.request,
|
|
253
|
+
} as SignedTransaction;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
256
|
+
throw new Error(`Failed to deserialize signed transaction: ${message}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Calculate checksum for data integrity
|
|
262
|
+
*
|
|
263
|
+
* @param data - Data to checksum
|
|
264
|
+
* @returns 4-byte checksum
|
|
265
|
+
*/
|
|
266
|
+
function calculateChecksum(data: Uint8Array): Uint8Array {
|
|
267
|
+
let checksum = 0;
|
|
268
|
+
for (let i = 0; i < data.length; i++) {
|
|
269
|
+
const byte = data[i];
|
|
270
|
+
if (byte !== undefined) {
|
|
271
|
+
checksum = ((checksum << 8) ^ byte) >>> 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const result = new Uint8Array(4);
|
|
276
|
+
const view = new DataView(result.buffer);
|
|
277
|
+
view.setUint32(0, checksum, false); // Big-endian
|
|
278
|
+
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Compare two buffers for equality
|
|
284
|
+
*
|
|
285
|
+
* @param a - First buffer
|
|
286
|
+
* @param b - Second buffer
|
|
287
|
+
* @returns True if buffers are equal
|
|
288
|
+
*/
|
|
289
|
+
function buffersEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
290
|
+
if (a.length !== b.length) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (let i = 0; i < a.length; i++) {
|
|
295
|
+
if (a[i] !== b[i]) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Validate CBOR data structure
|
|
305
|
+
*
|
|
306
|
+
* @param data - CBOR-encoded data
|
|
307
|
+
* @returns True if data appears valid
|
|
308
|
+
*/
|
|
309
|
+
export function validateCborData(data: Uint8Array): boolean {
|
|
310
|
+
try {
|
|
311
|
+
// Attempt to decode
|
|
312
|
+
const decoded = cbor.decode(data);
|
|
313
|
+
|
|
314
|
+
// Check if it's an object
|
|
315
|
+
if (!decoded || typeof decoded !== 'object' || decoded === null) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return true;
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|