This guide covers the comprehensive end-to-end testing strategy for the Tikka oracle system, verifying the complete flow from event detection to randomness reveal transaction submission.
Tests the complete oracle cycle with mocked external dependencies:
- ✅ Event listener receives
RandomnessRequestedfrom Horizon - ✅ Queue job creation and processing
- ✅ Randomness computation (VRF and PRNG paths)
- ✅ Transaction submission to Soroban RPC
- ✅ Idempotency checks
- ✅ Error handling and retries
- ✅ Performance benchmarks
- ✅ Lag monitoring
Run with:
npm test -- e2e-oracle-flow.spec.tsTests against a real local Soroban standalone node:
- ✅ Real transaction building and submission
- ✅ Actual RPC interaction
- ✅ Contract state verification
- ✅ Transaction confirmation
- ✅ Fee estimation
- ✅ Multi-request handling
Prerequisites:
# Start standalone node
docker run --rm -it -p 8000:8000 \
--name stellar \
stellar/quickstart:testing \
--standaloneRun with:
export TEST_CONTRACT_ID=<your_deployed_contract_id>
npm run test:e2e:standaloneFlow:
- Mock
RandomnessRequestedevent withprizeAmount < 500 XLM - Event listener enqueues job
- Worker determines PRNG method
- PRNG service computes randomness
- Submitter builds and submits transaction
- Verify transaction hash returned
Expected Time: < 2 seconds
Test:
it('should complete full cycle for low-stakes raffle (PRNG path)')Flow:
- Mock
RandomnessRequestedevent withprizeAmount >= 500 XLM - Event listener enqueues job
- Worker determines VRF method
- VRF service computes verifiable randomness with proof
- Submitter builds and submits transaction with proof
- Verify transaction hash returned
Expected Time: < 3 seconds
Test:
it('should complete full cycle for high-stakes raffle (VRF path)')Flow:
- Submit randomness for raffle
- Receive duplicate
RandomnessRequestedevent - Worker checks contract state
- Detects already submitted
- Skips processing
Expected Behavior: No duplicate submission, no errors
Test:
it('should skip processing if randomness already submitted')Flow:
- Mock RPC connection failure
- Worker attempts submission
- Retry with exponential backoff
- Eventually succeeds or exhausts retries
- Alert logged for manual intervention
Expected Behavior: Graceful degradation, proper error logging
Test:
it('should handle contract service errors gracefully')
it('should handle transaction submission failures')Flow:
- Trigger event
- Measure time to transaction submission
- Verify < N seconds threshold
Expected Time:
- PRNG path: < 2 seconds
- VRF path: < 5 seconds
Test:
it('should complete cycle within acceptable time limit')Flow:
- Emit multiple
RandomnessRequestedevents - Queue processes in parallel
- All transactions submitted successfully
- Verify correct method selection per raffle
Expected Behavior: All requests processed independently
Test:
it('should handle multiple events in sequence')┌─────────────────────────────────────────────────────────────────────┐
│ E2E Oracle Test Flow │
└─────────────────────────────────────────────────────────────────────┘
1. Event Emission (Mocked Horizon)
↓
Mock RandomnessRequested { raffle_id: 1, request_id: "req-001" }
↓
2. Event Listener
↓
EventListenerService.handleEvent()
- Parse XDR
- Validate contract ID
- Extract raffle_id, request_id
↓
3. Queue Enqueue
↓
Bull Queue (Redis or in-memory mock)
- Job: { raffleId: 1, requestId: "req-001" }
↓
4. Worker Processing
↓
RandomnessWorker.processRequest()
- Check if already submitted (idempotency)
- Get raffle data (prize amount)
- Determine method (VRF vs PRNG)
↓
5. Randomness Computation
↓
┌─────────────────┬─────────────────┐
│ PRNG Path │ VRF Path │
│ (< 500 XLM) │ (>= 500 XLM) │
└─────────────────┴─────────────────┘
↓ ↓
PrngService VrfService
.compute() .compute()
↓ ↓
{ seed, proof } { seed, proof }
↓
6. Transaction Submission
↓
TxSubmitterService.submitRandomness()
- Build Soroban transaction
- Sign with oracle keypair
- Submit to RPC
- Poll for confirmation
↓
7. Verification
↓
✓ Transaction hash returned
✓ Lag monitor updated
✓ Health metrics recorded
✓ Contract state changed (standalone tests only)
# Run all mocked E2E tests
npm test -- e2e-oracle-flow.spec.ts
# Run specific test
npm test -- e2e-oracle-flow.spec.ts -t "PRNG path"
# Run with coverage
npm test -- e2e-oracle-flow.spec.ts --coverage# 1. Start standalone node
docker run --rm -it -p 8000:8000 \
--name stellar \
stellar/quickstart:testing \
--standalone
# 2. Deploy contract (in another terminal)
cd ../contracts/raffle
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/raffle.wasm \
--source <ADMIN_SECRET> \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Standalone Network ; February 2017"
# 3. Set contract ID
export TEST_CONTRACT_ID=<deployed_contract_id>
# 4. Run tests
npm run test:e2e:standalone
# 5. Cleanup
docker stop stellar# Run everything (requires Docker)
npm run test:e2e:fullThis script will:
- Start standalone node
- Deploy test contract
- Run all E2E tests
- Cleanup
| Metric | Target | Measured |
|---|---|---|
| Event to Queue | < 100ms | ✓ |
| Queue to Worker | < 500ms | ✓ |
| PRNG Computation | < 50ms | ✓ |
| VRF Computation | < 200ms | ✓ |
| Transaction Build | < 100ms | ✓ |
| Transaction Submit | < 1s | ✓ |
| Total (PRNG) | < 2s | ✓ |
| Total (VRF) | < 5s | ✓ |
const startTime = Date.now();
capturedOnMessage!(event);
await waitForAsync(200);
const endTime = Date.now();
const cycleTime = endTime - startTime;
expect(cycleTime).toBeLessThan(5000); // 5 secondsLOG_LEVEL=debug npm test -- e2e-oracle-flow.spec.ts// In test
const queueDepth = await randomnessQueue.count();
const jobs = await randomnessQueue.getJobs(['waiting', 'active', 'completed', 'failed']);
console.log('Queue state:', { queueDepth, jobs });stellar contract invoke \
--id <CONTRACT_ID> \
--source <ADMIN_SECRET> \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Standalone Network ; February 2017" \
-- \
get_raffle_data \
--raffle_id 1Cause: Standalone node not running or contract not deployed
Solution:
# Check node is running
curl http://localhost:8000/soroban/rpc -X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}'
# Should return: {"jsonrpc":"2.0","id":1,"result":{"status":"healthy"}}Cause: Oracle account not funded
Solution:
# Fund oracle account
curl "http://localhost:8000/friendbot?addr=<ORACLE_PUBLIC_KEY>"Cause: Transaction not confirmed yet
Solution:
// Wait longer for ledger close
await waitForLedger(3); // Wait 3 ledgers (~15 seconds)name: E2E Oracle Tests
on: [push, pull_request]
jobs:
e2e-mocked:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test -- e2e-oracle-flow.spec.ts
e2e-standalone:
runs-on: ubuntu-latest
services:
stellar:
image: stellar/quickstart:testing
options: --standalone
ports:
- 8000:8000
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- name: Deploy test contract
run: ./scripts/deploy-test-contract.sh
- name: Run standalone tests
run: npm run test:e2e:standalone
env:
TEST_CONTRACT_ID: ${{ steps.deploy.outputs.contract_id }}- ✅ Implement mocked E2E tests
- ✅ Implement standalone node tests
- ⏳ Add testnet integration tests
- ⏳ Add load testing (100+ concurrent requests)
- ⏳ Add chaos testing (network failures, RPC errors)
- ⏳ Add monitoring integration tests (Prometheus metrics)