-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Enhance smoke tests with management API functionality #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |||||
| - XDR submit-only: signs a small self-payment and submits via fee bump | ||||||
| - func+auth (no auth): calls smoke-contract `no_auth_bump(42)` using a channel account | ||||||
| - func+auth (address auth): calls `write_with_address_auth(addr, 777)` and signs auth entries | ||||||
| - mgmt-api: tests management API (getFeeUsage, getFeeLimit, setFeeLimit, deleteFeeLimit) | ||||||
|
|
||||||
| Prerequisites | ||||||
| - Node.js 18+ (global fetch available) | ||||||
|
|
@@ -51,7 +52,7 @@ | |||||
| --network (NETWORK) default: testnet | also supports: mainnet | ||||||
| --rpc-url (RPC_URL) default: https://soroban-testnet.stellar.org | ||||||
| --test-id (TEST_ID) optional: run only one test | ||||||
| options: xdr-payment, func-auth-no-auth, func-auth-address-auth | ||||||
| options: xdr-payment, func-auth-no-auth, func-auth-address-auth, mgmt-api | ||||||
| --concurrency (CONCURRENCY) optional: parallel requests per test (default: 1) | ||||||
| --debug optional: print full plugin response with logs/traces | ||||||
| --api-key-header (API_KEY_HEADER) optional: header name for API key in relayer mode (default: x-api-key) | ||||||
|
|
@@ -113,7 +114,7 @@ | |||||
| const url = `${baseUrl}/api/v1/health`; | ||||||
| const res = await fetch(url, { | ||||||
| headers: { Authorization: `Bearer ${apiKey}` }, | ||||||
| } as any).catch((e: any) => ({ ok: false, statusText: e?.message || String(e) }) as any); | ||||||
| if (!res.ok) throw new Error(`Relayer health check failed: ${res.status} ${res.statusText}`); | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -154,14 +155,21 @@ | |||||
| const baseUrl = String(args['base-url'] || process.env.BASE_URL || (pluginId ? 'http://localhost:8080' : '')); | ||||||
| const network = String(args.network || process.env.NETWORK || 'testnet').toLowerCase() as 'testnet' | 'mainnet'; | ||||||
| const passphrase = np(network); | ||||||
| const rpcUrl = String(args['rpc-url'] || process.env.RPC_URL || 'https://soroban-testnet.stellar.org'); | ||||||
| const defaultRpcUrl = | ||||||
| network === 'mainnet' | ||||||
| ? 'https://soroban-rpc.mainnet.stellar.gateway.fm' | ||||||
| : 'https://soroban-testnet.stellar.org'; | ||||||
| const rpcUrl = String(args['rpc-url'] || process.env.RPC_URL || defaultRpcUrl); | ||||||
| const accountName = String(args['account-name'] || process.env.ACCOUNT_NAME || 'test-account'); | ||||||
| const testId = (args['test-id'] || process.env.TEST_ID) as string | undefined; | ||||||
| const debug = Boolean(args['debug'] || process.env.DEBUG); | ||||||
| const concurrency = parseInt(String(args['concurrency'] || process.env.CONCURRENCY || '1'), 10); | ||||||
| const contractId = String( | ||||||
| args['contract-id'] || process.env.CONTRACT_ID || 'CD3P6XI7YI6ATY5RM2CNXHRRT3LBGPC3WGR2D2OE6EQNVLVEA5HGUELG' | ||||||
| ); | ||||||
| // Default contract IDs per network | ||||||
| const defaultContractId = | ||||||
| network === 'mainnet' | ||||||
| ? 'CCP6S7LOAIWYZKFZQHVT44GAB7LDCXZEEZRAXVKCN4NWAWB4LEOXHWG4' // mainnet smoke contract | ||||||
| : 'CD3P6XI7YI6ATY5RM2CNXHRRT3LBGPC3WGR2D2OE6EQNVLVEA5HGUELG'; // testnet smoke contract | ||||||
| const contractId = String(args['contract-id'] || process.env.CONTRACT_ID || defaultContractId); | ||||||
|
|
||||||
| // Fee tracking flags | ||||||
| const apiKeyHeader = (args['api-key-header'] || process.env.API_KEY_HEADER || 'x-api-key') as string; | ||||||
|
|
@@ -200,8 +208,10 @@ | |||||
| address: string; | ||||||
| contractId: string; | ||||||
| debug: boolean; | ||||||
| apiKey: string; | ||||||
| adminSecret?: string; | ||||||
| }; | ||||||
| const ctx: Ctx = { client, rpc: rpcServer, passphrase, keypair, address, contractId, debug }; | ||||||
| const ctx: Ctx = { client, rpc: rpcServer, passphrase, keypair, address, contractId, debug, apiKey, adminSecret }; | ||||||
|
|
||||||
| const TESTS: { id: string; label: string; run: (ctx: Ctx) => Promise<void> }[] = [ | ||||||
| { | ||||||
|
|
@@ -246,6 +256,59 @@ | |||||
| printResult('func-auth-address-auth', { success: true, data: res }, debug); | ||||||
| }, | ||||||
| }, | ||||||
| { | ||||||
| id: 'mgmt-api', | ||||||
| label: 'Management API: fee limits CRUD', | ||||||
| run: async ({ client, apiKey, adminSecret, debug }) => { | ||||||
| if (!adminSecret) { | ||||||
| console.log(' ⏭ Skipped (requires --admin-secret)'); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| const formatStroops = (s: number) => `${s.toLocaleString()} stroops (${(s / 10_000_000).toFixed(7)} XLM)`; | ||||||
|
|
||||||
| // 1. Get current fee usage | ||||||
| const usage = await client.getFeeUsage(apiKey); | ||||||
| printResult('getFeeUsage', { success: true, data: usage }, debug); | ||||||
| if (!debug) console.log(` consumed: ${formatStroops(usage.consumed)}`); | ||||||
|
|
||||||
| // 2. Get current fee limit | ||||||
| const limitBefore = await client.getFeeLimit(apiKey); | ||||||
| printResult('getFeeLimit', { success: true, data: limitBefore }, debug); | ||||||
| if (!debug) console.log(` limit: ${limitBefore.limit ? formatStroops(limitBefore.limit) : 'unlimited'}`); | ||||||
|
|
||||||
| // 3. Set a custom fee limit (5 XLM) | ||||||
| const testLimit = 50_000_000; | ||||||
| const setResult = await client.setFeeLimit(apiKey, testLimit); | ||||||
| if (!setResult.ok || setResult.limit !== testLimit) { | ||||||
| throw new Error(`setFeeLimit failed: expected ${testLimit}, got ${setResult.limit}`); | ||||||
| } | ||||||
| printResult('setFeeLimit', { success: true, data: setResult }, debug); | ||||||
| if (!debug) console.log(` limit: ${formatStroops(setResult.limit)}`); | ||||||
|
|
||||||
| // 4. Verify limit was set | ||||||
| const limitAfterSet = await client.getFeeLimit(apiKey); | ||||||
| if (limitAfterSet.limit !== testLimit) { | ||||||
| throw new Error(`getFeeLimit after set: expected ${testLimit}, got ${limitAfterSet.limit}`); | ||||||
| } | ||||||
| printResult('getFeeLimit (verify set)', { success: true, data: limitAfterSet }, debug); | ||||||
|
|
||||||
| // 5. Delete the custom limit | ||||||
| const deleteResult = await client.deleteFeeLimit(apiKey); | ||||||
| if (!deleteResult.ok) { | ||||||
| throw new Error('deleteFeeLimit failed'); | ||||||
| } | ||||||
| printResult('deleteFeeLimit', { success: true, data: deleteResult }, debug); | ||||||
|
|
||||||
| // 6. Verify limit was deleted | ||||||
| const limitAfterDelete = await client.getFeeLimit(apiKey); | ||||||
| if (limitAfterDelete.limit === testLimit) { | ||||||
| throw new Error('getFeeLimit after delete: custom limit still present'); | ||||||
| } | ||||||
| printResult('getFeeLimit (verify deleted)', { success: true, data: limitAfterDelete }, debug); | ||||||
| if (!debug) console.log(` limit: ${limitAfterDelete.limit ? formatStroops(limitAfterDelete.limit) : 'unlimited'}`); | ||||||
| }, | ||||||
| }, | ||||||
| ]; | ||||||
|
|
||||||
| const selected = testId ? TESTS.filter((t) => t.id === testId) : TESTS; | ||||||
|
|
@@ -354,18 +417,24 @@ | |||||
|
|
||||||
| function printResult(label: string, envelope: any, debug: boolean) { | ||||||
| if (debug) { | ||||||
| // Pretty print the full envelope including data, metadata (logs/traces) | ||||||
| console.log(` ${label}:`); | ||||||
| console.log(JSON.stringify(envelope, null, 2)); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| const data = envelope?.data || envelope?.result || {}; | ||||||
| const hash = data?.hash; | ||||||
| const status = data?.status; | ||||||
| const ok = data?.ok; | ||||||
| const success = envelope?.success; | ||||||
|
|
||||||
| if (success) { | ||||||
| console.log(` ✓ ${label}: ${hash || status || 'confirmed'}`); | ||||||
| // For tx results, show hash; for mgmt results, show ok or just confirmed | ||||||
| const detail = hash || status || (ok !== undefined ? `ok=${ok}` : 'ok'); | ||||||
| console.log(` ${label}: ${detail}`); | ||||||
|
||||||
| console.log(` ${label}: ${detail}`); | |
| console.log(` ✓ ${label}: ${detail}`); |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error output format has been changed from "✗" to no symbol and from just the error to "FAILED - " prefix. While this might be intentional, the removal of the "✗" symbol makes it less visually distinct from success messages. Consider retaining the "✗" symbol for better visual distinction between success and failure cases.
| console.log(` ${label}: FAILED - ${error}`); | |
| console.log(` ${label}: ✗ FAILED - ${error}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Save and restore initial state to avoid side effects.
The management API test modifies state (sets and deletes fee limits) without restoring the original state. If a custom limit exists before the test runs, it will be lost after the test completes. This can affect subsequent tests or ongoing operations.
Additionally, the verification at lines 305-307 only checks that the test limit was removed, not that the original state was restored.
🔎 Apply this diff to save and restore the initial state:
run: async ({ client, apiKey, adminSecret, debug }) => { if (!adminSecret) { console.log(' ⏭ Skipped (requires --admin-secret)'); return; } const formatStroops = (s: number) => `${s.toLocaleString()} stroops (${(s / 10_000_000).toFixed(7)} XLM)`; // 1. Get current fee usage const usage = await client.getFeeUsage(apiKey); printResult('getFeeUsage', { success: true, data: usage }, debug); if (!debug) console.log(` consumed: ${formatStroops(usage.consumed)}`); // 2. Get current fee limit (save for restoration) const limitBefore = await client.getFeeLimit(apiKey); printResult('getFeeLimit', { success: true, data: limitBefore }, debug); if (!debug) console.log(` limit: ${limitBefore.limit ? formatStroops(limitBefore.limit) : 'unlimited'}`); + const originalLimit = limitBefore.limit; // 3. Set a custom fee limit (5 XLM) const testLimit = 50_000_000; const setResult = await client.setFeeLimit(apiKey, testLimit); if (!setResult.ok || setResult.limit !== testLimit) { throw new Error(`setFeeLimit failed: expected ${testLimit}, got ${setResult.limit}`); } printResult('setFeeLimit', { success: true, data: setResult }, debug); if (!debug) console.log(` limit: ${formatStroops(setResult.limit)}`); // 4. Verify limit was set const limitAfterSet = await client.getFeeLimit(apiKey); if (limitAfterSet.limit !== testLimit) { throw new Error(`getFeeLimit after set: expected ${testLimit}, got ${limitAfterSet.limit}`); } printResult('getFeeLimit (verify set)', { success: true, data: limitAfterSet }, debug); // 5. Delete the custom limit const deleteResult = await client.deleteFeeLimit(apiKey); if (!deleteResult.ok) { throw new Error('deleteFeeLimit failed'); } printResult('deleteFeeLimit', { success: true, data: deleteResult }, debug); // 6. Verify limit was deleted const limitAfterDelete = await client.getFeeLimit(apiKey); if (limitAfterDelete.limit === testLimit) { throw new Error('getFeeLimit after delete: custom limit still present'); } printResult('getFeeLimit (verify deleted)', { success: true, data: limitAfterDelete }, debug); if (!debug) console.log(` limit: ${limitAfterDelete.limit ? formatStroops(limitAfterDelete.limit) : 'unlimited'}`); + + // 7. Restore original state + if (originalLimit !== undefined && originalLimit !== null) { + const restoreResult = await client.setFeeLimit(apiKey, originalLimit); + if (!restoreResult.ok || restoreResult.limit !== originalLimit) { + console.warn(` ⚠ Failed to restore original limit: expected ${originalLimit}, got ${restoreResult.limit}`); + } else { + if (!debug) console.log(` restored original limit: ${formatStroops(originalLimit)}`); + } + } },📝 Committable suggestion
🤖 Prompt for AI Agents