ape-claw 0.1.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/.cursor/skills/ape-claw/SKILL.md +322 -0
- package/LICENSE +21 -0
- package/README.md +826 -0
- package/allowlists/opensea-slug-overrides.json +13 -0
- package/allowlists/recommended.apechain.json +322 -0
- package/config/clawbots.example.json +3 -0
- package/config/policy.example.json +27 -0
- package/data/starter-pack-bundle.json +1 -0
- package/data/starter-pack.json +495 -0
- package/docs/ACP_BOUNTIES.md +108 -0
- package/docs/APECLAW_V2_ALPHA.md +206 -0
- package/docs/AUTONOMY_AND_SUBSTRATE.md +69 -0
- package/docs/CLAWBOTS_AND_INVITES.md +102 -0
- package/docs/CLI_GUIDE.md +124 -0
- package/docs/CONTRIBUTING.md +130 -0
- package/docs/DASHBOARD_GUIDE.md +108 -0
- package/docs/GLOBAL_BACKEND.md +145 -0
- package/docs/ONCHAIN_V2_GUIDE.md +140 -0
- package/docs/PRODUCT_OVERVIEW.md +127 -0
- package/docs/README.md +40 -0
- package/docs/SKILLCARDS_AND_IMPORTER.md +147 -0
- package/docs/STARTER_PACK.md +297 -0
- package/docs/SUPPORTED_NETWORKS.md +58 -0
- package/docs/TELEMETRY_AND_EVENTS.md +103 -0
- package/docs/THE_POD_RUNNER.md +198 -0
- package/docs/V1_WORKFLOWS.md +108 -0
- package/docs/V2_ONCHAIN_SKILLS.md +157 -0
- package/docs/WEB4_PLAN_STATUS.md +95 -0
- package/docs/WEB4_SWARM_MODEL.md +104 -0
- package/docs/archive/AUTONOMY_AND_SUBSTRATE.md +66 -0
- package/docs/archive/WEB4_PLAN_STATUS.md +93 -0
- package/docs/archive/WEB4_SWARM_MODEL.md +98 -0
- package/docs/developer/01-architecture.md +345 -0
- package/docs/developer/02-contracts.md +1034 -0
- package/docs/developer/03-writing-modules.md +513 -0
- package/docs/developer/04-skillcard-spec.md +336 -0
- package/docs/developer/05-backend-api.md +1079 -0
- package/docs/developer/06-telemetry.md +798 -0
- package/docs/developer/07-testing.md +546 -0
- package/docs/developer/08-contributing.md +211 -0
- package/docs/operator/01-quickstart.md +49 -0
- package/docs/operator/02-dashboard.md +174 -0
- package/docs/operator/03-cli-reference.md +818 -0
- package/docs/operator/04-skills-library.md +169 -0
- package/docs/operator/05-pod-operations.md +314 -0
- package/docs/operator/06-deployment.md +299 -0
- package/docs/operator/07-safety-and-policy.md +311 -0
- package/docs/operator/08-troubleshooting.md +457 -0
- package/docs/operator/09-env-reference.md +238 -0
- package/docs/social/STARTER_PACK_THREAD.md +209 -0
- package/package.json +77 -0
- package/skillcards/import-sources.json +93 -0
- package/skillcards/seed/acp-bounty-poll.v1.json +38 -0
- package/skillcards/seed/acp-bounty-post.v1.json +55 -0
- package/skillcards/seed/acp-browse.v1.json +41 -0
- package/skillcards/seed/acp-fulfill-and-route.v1.json +56 -0
- package/skillcards/seed/apeclaw-bridge-relay.v1.json +46 -0
- package/skillcards/seed/apeclaw-nft-autobuy.v1.json +60 -0
- package/skillcards/seed/apeclaw-receipt-recorder.v1.json +64 -0
- package/skillcards/seed/humanizer.v1.json +74 -0
- package/skillcards/seed/otherside-navigator.v1.json +116 -0
- package/skillcards/seed/stonkbrokers-launcher.v1.json +280 -0
- package/skillcards/seed/walkie-p2p.v1.json +66 -0
- package/src/cli/index.mjs +8 -0
- package/src/cli.mjs +1929 -0
- package/src/lib/bridge-relay.mjs +294 -0
- package/src/lib/clawbots.mjs +94 -0
- package/src/lib/io.mjs +36 -0
- package/src/lib/market.mjs +233 -0
- package/src/lib/nft-opensea.mjs +159 -0
- package/src/lib/paths.mjs +17 -0
- package/src/lib/pod-init.mjs +40 -0
- package/src/lib/policy.mjs +112 -0
- package/src/lib/rpc.mjs +49 -0
- package/src/lib/telemetry.mjs +92 -0
- package/src/lib/v2-onchain-abi.mjs +294 -0
- package/src/lib/v2-skillcard.mjs +27 -0
- package/src/server/index.mjs +169 -0
- package/src/server/logger.mjs +21 -0
- package/src/server/middleware/auth.mjs +90 -0
- package/src/server/middleware/body-limit.mjs +35 -0
- package/src/server/middleware/cors.mjs +33 -0
- package/src/server/middleware/rate-limit.mjs +44 -0
- package/src/server/routes/chat.mjs +178 -0
- package/src/server/routes/clawbots.mjs +182 -0
- package/src/server/routes/events.mjs +95 -0
- package/src/server/routes/health.mjs +72 -0
- package/src/server/routes/pod.mjs +64 -0
- package/src/server/routes/quotes.mjs +161 -0
- package/src/server/routes/skills.mjs +239 -0
- package/src/server/routes/static.mjs +161 -0
- package/src/server/routes/v2.mjs +48 -0
- package/src/server/sse.mjs +73 -0
- package/src/server/storage/file-backend.mjs +295 -0
- package/src/server/storage/index.mjs +37 -0
- package/src/server/storage/sqlite-backend.mjs +380 -0
- package/src/telemetry-server.mjs +1604 -0
- package/ui/css/dashboard.css +792 -0
- package/ui/css/skills.css +689 -0
- package/ui/docs.html +840 -0
- package/ui/favicon-180.png +0 -0
- package/ui/favicon-192.png +0 -0
- package/ui/favicon-32.png +0 -0
- package/ui/favicon-lobster.png +0 -0
- package/ui/favicon.svg +10 -0
- package/ui/index.html +2957 -0
- package/ui/js/dashboard.js +1766 -0
- package/ui/js/skills.js +1621 -0
- package/ui/pod.html +909 -0
- package/ui/shared/motion.css +286 -0
- package/ui/shared/motion.js +170 -0
- package/ui/shared/sidebar-nav.css +379 -0
- package/ui/shared/sidebar-nav.js +137 -0
- package/ui/skills.html +2879 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
ApeClaw uses a multi-layered testing approach:
|
|
6
|
+
- **Smart Contract Tests**: Hardhat + viem for onchain contract testing
|
|
7
|
+
- **CLI Tests**: Node.js built-in test runner for command-line interface testing
|
|
8
|
+
- **Policy Tests**: Unit tests for policy enforcement logic
|
|
9
|
+
- **Integration Tests**: End-to-end workflows
|
|
10
|
+
|
|
11
|
+
## Smart Contract Tests
|
|
12
|
+
|
|
13
|
+
### Running Tests
|
|
14
|
+
|
|
15
|
+
Run all contract tests:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run contracts:test
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This runs Hardhat tests using the Node.js test runner. For Solidity-specific tests:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm run contracts:test:solidity
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Compile contracts first:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm run contracts:compile
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Test Structure
|
|
34
|
+
|
|
35
|
+
Contract tests live in `contracts-test/` directory:
|
|
36
|
+
|
|
37
|
+
- **`v2-registry.test.js`**: SkillNFT minting/ownership, SkillRegistry version publishing, PodVault royalty routing, IntentRegistry create/cancel flows, ReceiptRegistry immutable receipt recording, AgentAccount + PolicyEngine integration, module execution with policy gating
|
|
38
|
+
- **`policy-engine.test.js`**: PolicyEngine preCheck passes/reverts, value cap enforcement, owner-only access control
|
|
39
|
+
|
|
40
|
+
### Writing Tests
|
|
41
|
+
|
|
42
|
+
Tests use Hardhat's viem integration with Node.js test runner:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
import assert from "node:assert/strict";
|
|
46
|
+
import { describe, it } from "node:test";
|
|
47
|
+
import hre from "hardhat";
|
|
48
|
+
import { keccak256, toHex } from "viem";
|
|
49
|
+
|
|
50
|
+
const { viem } = await hre.network.connect();
|
|
51
|
+
|
|
52
|
+
describe("MyContract", () => {
|
|
53
|
+
it("does something", async () => {
|
|
54
|
+
const publicClient = await viem.getPublicClient();
|
|
55
|
+
const contract = await viem.deployContract("MyContract");
|
|
56
|
+
|
|
57
|
+
// Test logic here
|
|
58
|
+
const result = await contract.read.someFunction();
|
|
59
|
+
assert.equal(result, expectedValue);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Test Patterns
|
|
65
|
+
|
|
66
|
+
#### Testing SkillNFT
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
it("mints SkillNFT and publishes immutable version", async () => {
|
|
70
|
+
const publicClient = await viem.getPublicClient();
|
|
71
|
+
|
|
72
|
+
const skillNft = await viem.deployContract("SkillNFT");
|
|
73
|
+
const registry = await viem.deployContract("SkillRegistry", [skillNft.address]);
|
|
74
|
+
|
|
75
|
+
const mintTx = await skillNft.write.mintSkill([0n]);
|
|
76
|
+
await publicClient.waitForTransactionReceipt({ hash: mintTx });
|
|
77
|
+
|
|
78
|
+
const owner = await skillNft.read.ownerOf([1n]);
|
|
79
|
+
assert.ok(owner, "owner should exist");
|
|
80
|
+
|
|
81
|
+
const versionHash = keccak256(toHex("v1.0.0"));
|
|
82
|
+
const contentHash = keccak256(toHex('{"name":"demo-skill","version":"1.0.0"}'));
|
|
83
|
+
|
|
84
|
+
const pubTx = await registry.write.publishVersion([
|
|
85
|
+
1n,
|
|
86
|
+
versionHash,
|
|
87
|
+
contentHash,
|
|
88
|
+
"ipfs://example",
|
|
89
|
+
1,
|
|
90
|
+
]);
|
|
91
|
+
await publicClient.waitForTransactionReceipt({ hash: pubTx });
|
|
92
|
+
|
|
93
|
+
const count = await registry.read.versionCount([1n]);
|
|
94
|
+
assert.equal(count, 1n);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Testing AgentAccount + PolicyEngine Integration
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
it("executes a policy-gated module call and records a receipt", async () => {
|
|
102
|
+
const publicClient = await viem.getPublicClient();
|
|
103
|
+
const [walletClient] = await viem.getWalletClients();
|
|
104
|
+
|
|
105
|
+
const receipts = await viem.deployContract("ReceiptRegistry");
|
|
106
|
+
const policy = await viem.deployContract("PolicyEngine", [walletClient.account.address]);
|
|
107
|
+
const agent = await viem.deployContract("AgentAccount", [
|
|
108
|
+
walletClient.account.address,
|
|
109
|
+
policy.address,
|
|
110
|
+
receipts.address
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const module = await viem.deployContract("SwapModule");
|
|
114
|
+
const target = await viem.deployContract("MockTarget");
|
|
115
|
+
|
|
116
|
+
// Configure policy: allow module, target, and selector
|
|
117
|
+
await policy.write.setModuleAllowed([module.address, true]);
|
|
118
|
+
await policy.write.setTargetAllowed([target.address, true]);
|
|
119
|
+
await policy.write.setSelectorAllowed([target.address, selector, true]);
|
|
120
|
+
|
|
121
|
+
// Execute through AgentAccount
|
|
122
|
+
const tx = await agent.write.executeSkill([
|
|
123
|
+
module.address,
|
|
124
|
+
input,
|
|
125
|
+
0n,
|
|
126
|
+
traceIdHash,
|
|
127
|
+
subjectHash,
|
|
128
|
+
uri
|
|
129
|
+
]);
|
|
130
|
+
await publicClient.waitForTransactionReceipt({ hash: tx });
|
|
131
|
+
|
|
132
|
+
// Verify receipt was recorded
|
|
133
|
+
const ok = await receipts.read.isRecorded([traceIdHash]);
|
|
134
|
+
assert.equal(ok, true);
|
|
135
|
+
|
|
136
|
+
// Test fail-closed behavior: block selector and confirm it fails
|
|
137
|
+
await policy.write.setSelectorAllowed([target.address, selector, false]);
|
|
138
|
+
let threw = false;
|
|
139
|
+
try {
|
|
140
|
+
await agent.write.executeSkill([...]);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
threw = true;
|
|
143
|
+
}
|
|
144
|
+
assert.equal(threw, true);
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Local Devnet Testing
|
|
149
|
+
|
|
150
|
+
#### Running Hardhat Node
|
|
151
|
+
|
|
152
|
+
Start a local Hardhat node:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npx hardhat node
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This starts a local Ethereum node on `http://127.0.0.1:8545` with 20 pre-funded accounts.
|
|
159
|
+
|
|
160
|
+
#### Deploying with Seed Script
|
|
161
|
+
|
|
162
|
+
Deploy contracts and seed SkillCards:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm run contracts:seed
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Or explicitly target localhost:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npx hardhat run contracts-scripts/deploy-and-seed-v2-alpha.js --network localhost
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The seed script:
|
|
175
|
+
1. Deploys all v2 contracts (SkillNFT, SkillRegistry, PolicyEngine, AgentAccount, etc.)
|
|
176
|
+
2. Configures PolicyEngine with module allowlists
|
|
177
|
+
3. Reads all JSON SkillCards from `skillcards/seed/`
|
|
178
|
+
4. Mints SkillNFTs and publishes versions to the registry
|
|
179
|
+
5. Outputs contract addresses and deployment info to `state/v2-deployments/localhost.json`
|
|
180
|
+
|
|
181
|
+
#### Testing the Full Flow
|
|
182
|
+
|
|
183
|
+
1. **Start local node**:
|
|
184
|
+
```bash
|
|
185
|
+
npx hardhat node
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
2. **In another terminal, deploy contracts**:
|
|
189
|
+
```bash
|
|
190
|
+
npm run contracts:seed
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
3. **Export contract addresses** (from seed output):
|
|
194
|
+
```bash
|
|
195
|
+
export APE_CLAW_V2_SKILL_NFT=0x...
|
|
196
|
+
export APE_CLAW_V2_SKILL_REGISTRY=0x...
|
|
197
|
+
export APE_CLAW_V2_POLICY_ENGINE=0x...
|
|
198
|
+
export APE_CLAW_V2_AGENT_ACCOUNT=0x...
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
4. **Test CLI commands**:
|
|
202
|
+
```bash
|
|
203
|
+
node ./src/cli.mjs doctor --json
|
|
204
|
+
node ./src/cli.mjs chain info --json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
5. **Verify onchain state**:
|
|
208
|
+
```bash
|
|
209
|
+
# Query SkillRegistry
|
|
210
|
+
node -e "
|
|
211
|
+
import { createPublicClient, http } from 'viem';
|
|
212
|
+
import { hardhat } from 'viem/chains';
|
|
213
|
+
const client = createPublicClient({ chain: hardhat, transport: http() });
|
|
214
|
+
// Query contract...
|
|
215
|
+
"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## CLI Tests
|
|
219
|
+
|
|
220
|
+
### Running CLI Tests
|
|
221
|
+
|
|
222
|
+
Run all CLI tests:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm test
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
This runs `hardhat compile && node --test`, which:
|
|
229
|
+
1. Compiles Solidity contracts
|
|
230
|
+
2. Runs all tests in `test/` directory using Node.js test runner
|
|
231
|
+
|
|
232
|
+
### Test Files
|
|
233
|
+
|
|
234
|
+
- `test/policy.test.mjs`: Policy enforcement unit tests
|
|
235
|
+
- `test/cli.test.mjs`: CLI command integration tests
|
|
236
|
+
- `test/server.test.mjs`: Server integration tests (health, events, skills, clawbots, CORS, rate limiting, body limits, backlog, quotes auth)
|
|
237
|
+
|
|
238
|
+
### Writing CLI Tests
|
|
239
|
+
|
|
240
|
+
CLI tests use Node.js built-in test runner:
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
import test from "node:test";
|
|
244
|
+
import assert from "node:assert/strict";
|
|
245
|
+
import { execSync } from "node:child_process";
|
|
246
|
+
|
|
247
|
+
function run(cmd) {
|
|
248
|
+
return execSync(cmd, { cwd: process.cwd(), encoding: "utf8" });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
test("doctor command returns json with chainId", () => {
|
|
252
|
+
const out = run("node ./src/cli.mjs doctor --json");
|
|
253
|
+
const data = JSON.parse(out);
|
|
254
|
+
assert.equal(data.chainId, 33139);
|
|
255
|
+
assert.ok(Array.isArray(data.issues));
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Common Test Scenarios
|
|
260
|
+
|
|
261
|
+
#### Testing JSON Output
|
|
262
|
+
|
|
263
|
+
All commands support `--json` flag for structured output:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
test("command returns valid JSON", () => {
|
|
267
|
+
const out = run("node ./src/cli.mjs doctor --json");
|
|
268
|
+
const data = JSON.parse(out);
|
|
269
|
+
assert.equal(data.ok, true);
|
|
270
|
+
assert.ok(typeof data.chainId === "number");
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Testing Error Handling
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
function runFail(cmd) {
|
|
278
|
+
try {
|
|
279
|
+
execSync(cmd, { cwd: process.cwd(), encoding: "utf8", stdio: "pipe" });
|
|
280
|
+
return "";
|
|
281
|
+
} catch (err) {
|
|
282
|
+
return `${err.stdout}\n${err.stderr}`.trim();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
test("nft buy requires simulation first", () => {
|
|
287
|
+
const msg = runFail("node ./src/cli.mjs nft buy --quote q_test --execute --json");
|
|
288
|
+
assert.match(msg, /Simulation required before execute/);
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
#### Testing Policy Enforcement
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
test("buy policy blocks disallowed currency", () => {
|
|
296
|
+
const res = enforceBuyPolicy({
|
|
297
|
+
policy,
|
|
298
|
+
collection: "DSNRS",
|
|
299
|
+
maxPrice: 10,
|
|
300
|
+
currency: "USDC", // Not in allowlist
|
|
301
|
+
allowlist: [{ name: "DSNRS" }],
|
|
302
|
+
});
|
|
303
|
+
assert.equal(res.ok, false);
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## API Testing
|
|
308
|
+
|
|
309
|
+
### Coverage
|
|
310
|
+
|
|
311
|
+
Run tests with `c8` code coverage:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npm run test:coverage
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
This generates a coverage report for CLI, policy, and server tests.
|
|
318
|
+
|
|
319
|
+
### Telemetry Server Endpoints
|
|
320
|
+
|
|
321
|
+
The telemetry server (`src/server/index.mjs`) exposes several endpoints:
|
|
322
|
+
|
|
323
|
+
- `GET /`: Dashboard UI
|
|
324
|
+
- `GET /events`: Server-Sent Events stream
|
|
325
|
+
- `GET /events/backlog`: Historical events (JSONL)
|
|
326
|
+
- `GET /api/allowlist`: Collection metadata
|
|
327
|
+
- `POST /api/clawbots/register`: Register a clawbot
|
|
328
|
+
- `POST /api/clawbots/verify`: Verify clawbot token
|
|
329
|
+
|
|
330
|
+
### Testing with curl
|
|
331
|
+
|
|
332
|
+
#### Check Server Health
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
curl http://localhost:8787/
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### Get Event Backlog
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
curl http://localhost:8787/events/backlog | head -20
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### Register Clawbot
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
curl -X POST http://localhost:8787/api/clawbots/register \
|
|
348
|
+
-H "Content-Type: application/json" \
|
|
349
|
+
-d '{
|
|
350
|
+
"agentId": "test-bot",
|
|
351
|
+
"name": "Test Bot",
|
|
352
|
+
"api": "https://apeclaw.ai"
|
|
353
|
+
}'
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### Verify Clawbot Token
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
curl "http://localhost:8787/api/clawbots/verify?agentId=test-bot&token=claw_..."
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Stream Live Events (SSE)
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
curl -N http://localhost:8787/events
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Common Test Scenarios
|
|
369
|
+
|
|
370
|
+
#### Test Event Emission
|
|
371
|
+
|
|
372
|
+
1. Start telemetry server:
|
|
373
|
+
```bash
|
|
374
|
+
npm run telemetry
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
2. Run CLI command that emits events:
|
|
378
|
+
```bash
|
|
379
|
+
node ./src/cli.mjs doctor --json
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
3. Check events in backlog:
|
|
383
|
+
```bash
|
|
384
|
+
curl http://localhost:8787/events/backlog | jq '.[-1]'
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Test SSE Stream
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
# Terminal 1: Start server
|
|
391
|
+
npm run telemetry
|
|
392
|
+
|
|
393
|
+
# Terminal 2: Stream events
|
|
394
|
+
curl -N http://localhost:8787/events
|
|
395
|
+
|
|
396
|
+
# Terminal 3: Trigger event
|
|
397
|
+
node ./src/cli.mjs chain info --json
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Integration Testing
|
|
401
|
+
|
|
402
|
+
### Full Flow: Deploy → Seed → Mint → Verify
|
|
403
|
+
|
|
404
|
+
1. **Start local Hardhat node**:
|
|
405
|
+
```bash
|
|
406
|
+
npx hardhat node
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
2. **Deploy contracts and seed skills**:
|
|
410
|
+
```bash
|
|
411
|
+
npm run contracts:seed
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
3. **Export contract addresses** (from seed output):
|
|
415
|
+
```bash
|
|
416
|
+
export APE_CLAW_V2_SKILL_NFT=0x...
|
|
417
|
+
export APE_CLAW_V2_SKILL_REGISTRY=0x...
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
4. **Mint a SkillNFT via CLI** (if CLI command exists):
|
|
421
|
+
```bash
|
|
422
|
+
node ./src/cli.mjs skill mint --skillcard skillcards/seed/apeclaw-nft-autobuy.json --json
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
5. **Verify onchain**:
|
|
426
|
+
```bash
|
|
427
|
+
# Query SkillRegistry for published versions
|
|
428
|
+
node -e "
|
|
429
|
+
import { createPublicClient, http } from 'viem';
|
|
430
|
+
import { hardhat } from 'viem/chains';
|
|
431
|
+
const client = createPublicClient({ chain: hardhat, transport: http() });
|
|
432
|
+
// Query registry...
|
|
433
|
+
"
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
6. **Verify in UI**:
|
|
437
|
+
- Start telemetry server: `npm run telemetry`
|
|
438
|
+
- Open `http://localhost:8787`
|
|
439
|
+
- Check that events appear in dashboard
|
|
440
|
+
|
|
441
|
+
### Testing Quote → Simulate → Execute Flow
|
|
442
|
+
|
|
443
|
+
1. **Create quote**:
|
|
444
|
+
```bash
|
|
445
|
+
node ./src/cli.mjs nft quote-buy \
|
|
446
|
+
--collection "Mintotaurs" \
|
|
447
|
+
--tokenId 123 \
|
|
448
|
+
--maxPrice 40 \
|
|
449
|
+
--currency APE \
|
|
450
|
+
--json
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
2. **Simulate**:
|
|
454
|
+
```bash
|
|
455
|
+
node ./src/cli.mjs nft simulate --quote <quoteId> --json
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
3. **Execute** (dry-run by default):
|
|
459
|
+
```bash
|
|
460
|
+
node ./src/cli.mjs nft buy --quote <quoteId> --execute --json
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
4. **Verify telemetry events**:
|
|
464
|
+
```bash
|
|
465
|
+
curl http://localhost:8787/events/backlog | jq '.[] | select(.eventType == "nft.buy.confirmed")'
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Negative Test Cases
|
|
469
|
+
|
|
470
|
+
Test fail-closed behavior:
|
|
471
|
+
|
|
472
|
+
- **Stale quote**: Quote expires before execution
|
|
473
|
+
- **Fee cap breach**: Bridge fee exceeds `maxBridgeFeeBps`
|
|
474
|
+
- **Disallowed collection**: Collection not in allowlist
|
|
475
|
+
- **Disallowed currency**: Currency not in `currencyAllowlist`
|
|
476
|
+
- **Price cap breach**: Price exceeds `maxPricePerTx`
|
|
477
|
+
- **Missing simulation**: Execute without simulate step
|
|
478
|
+
- **Missing confirm phrase**: Execute without confirmation
|
|
479
|
+
|
|
480
|
+
Example:
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
test("nft buy execute requires simulation first", () => {
|
|
484
|
+
const quote = { quoteId: "q_test", /* ... */ };
|
|
485
|
+
// Save quote to state/quotes.json
|
|
486
|
+
const msg = runFail("node ./src/cli.mjs nft buy --quote q_test --execute --json");
|
|
487
|
+
assert.match(msg, /Simulation required before execute/);
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Continuous Integration
|
|
492
|
+
|
|
493
|
+
Tests run automatically on:
|
|
494
|
+
- Pull requests (via GitHub Actions)
|
|
495
|
+
- Pre-publish hook (`prepublishOnly` script)
|
|
496
|
+
|
|
497
|
+
See `.github/workflows/ci.yml` for CI configuration.
|
|
498
|
+
|
|
499
|
+
## Test Coverage Goals
|
|
500
|
+
|
|
501
|
+
- **Smart Contracts**: All public functions should have tests
|
|
502
|
+
- **Policy Engine**: All policy enforcement paths should be tested
|
|
503
|
+
- **CLI Commands**: All commands should have JSON output tests
|
|
504
|
+
- **Negative Cases**: Fail-closed behavior must be verified
|
|
505
|
+
- **Integration**: End-to-end flows should be tested locally
|
|
506
|
+
|
|
507
|
+
## Debugging Tests
|
|
508
|
+
|
|
509
|
+
### Hardhat Tests
|
|
510
|
+
|
|
511
|
+
Enable verbose logging:
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
DEBUG=hardhat:* npm run contracts:test
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### CLI Tests
|
|
518
|
+
|
|
519
|
+
Run a specific test file:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
node --test test/policy.test.mjs
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Run with verbose output:
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
node --test --test-reporter=spec test/cli.test.mjs
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Local Devnet Debugging
|
|
532
|
+
|
|
533
|
+
1. Start Hardhat node with verbose logging:
|
|
534
|
+
```bash
|
|
535
|
+
npx hardhat node --verbose
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
2. In another terminal, run tests with network targeting:
|
|
539
|
+
```bash
|
|
540
|
+
npx hardhat test --network localhost
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
3. Use Hardhat console to inspect state:
|
|
544
|
+
```bash
|
|
545
|
+
npx hardhat console --network localhost
|
|
546
|
+
```
|