This document describes the End-to-End (E2E) testing infrastructure for the Predictify backend. E2E tests validate the complete prediction market lifecycle against real Stellar testnet infrastructure.
E2E tests ensure that:
- The complete prediction lifecycle works end-to-end
- Integration with Stellar/Soroban testnet is functional
- Database state remains consistent across operations
- Authentication and authorization work correctly
- Real-world scenarios are validated before production deployment
This test validates the complete user journey:
- Authentication: User authenticates with Stellar wallet signature
- Market Creation: A new prediction market is created on testnet
- Place Prediction: User places a prediction with specific outcome and amount
- Market Resolution: Market resolves with a winning outcome
- Claim Winnings: User claims their winnings from the resolved market
- Data Consistency: Verifies all data relationships and state transitions
Each step includes:
- Structured logging with correlation IDs
- State validation
- Error handling
- Cleanup on completion or failure
You need a Stellar testnet account with:
- Sufficient XLM balance for transaction fees
- Sufficient test tokens for predictions
- The secret key stored securely
To create and fund a testnet account:
# Generate keypair (or use Stellar Laboratory)
# Visit: https://laboratory.stellar.org/#account-creator
# Fund the account with Friendbot
curl "https://friendbot.stellar.org?addr=YOUR_PUBLIC_KEY"The E2E tests require a deployed Predictify contract on testnet:
- Contract must be initialized
- Contract ID must be set in environment variables
A PostgreSQL database for test data:
- Separate from production and development databases
- Automatically seeded and cleaned up by tests
Create a .env.e2e file with the following variables:
# ── Test Account ──────────────────────────────────────────
# Stellar testnet account secret key (required)
E2E_TEST_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ── Stellar/Soroban ───────────────────────────────────────
STELLAR_NETWORK=testnet
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
HORIZON_URL=https://horizon-testnet.stellar.org
# Deployed contract ID on testnet (required)
PREDICTIFY_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ── Database ──────────────────────────────────────────────
# Separate test database
DATABASE_URL=postgres://postgres:postgres@localhost:5432/predictify_e2e
# ── Redis ─────────────────────────────────────────────────
REDIS_URL=redis://localhost:6379
# ── JWT ───────────────────────────────────────────────────
JWT_SECRET=e2e-test-jwt-secret-that-is-at-least-32-characters-long
JWT_ISSUER=predictify-e2e
JWT_AUDIENCE=predictify-e2e-app
# ── Application ───────────────────────────────────────────
NODE_ENV=test
PORT=3001
LOG_LEVEL=info
# Disable rate limiting for E2E tests
ANON_RATE_LIMIT_MAX=10000
CAPTCHA_THRESHOLD=0# 1. Ensure test database is running
docker run -d \
--name predictify-e2e-db \
-e POSTGRES_DB=predictify_e2e \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
postgres:16-alpine
# 2. Run migrations
npm run db:migrate
# 3. Run E2E tests
npm test -- tests/e2e/predictionLifecycle.test.ts
# 4. Run with verbose output
npm test -- tests/e2e/predictionLifecycle.test.ts --verbose
# 5. Run with coverage
npm test -- tests/e2e/predictionLifecycle.test.ts --coverageE2E tests run automatically:
Nightly Schedule: Every day at 2 AM UTC
- Validates testnet integration remains functional
- Catches issues with external dependencies
- Creates GitHub issues on failure
Manual Trigger: Via workflow_dispatch
- For on-demand validation
- Before major releases
- After infrastructure changes
On Push: When E2E test files are modified
- Immediate feedback on test changes
- Validates test refactoring
Configure the following secrets in your GitHub repository:
Settings → Secrets and variables → Actions → New repository secret
| Secret Name | Description | Example |
|---|---|---|
E2E_TEST_SECRET_KEY |
Testnet account secret key | SXXXXX... |
TESTNET_CONTRACT_ID |
Deployed contract ID | CXXXXX... |
E2E_JWT_SECRET |
JWT secret for CI tests | random-32-char-string |
E2E_ADMIN_ADDRESS |
Admin Stellar address (optional) | GXXXXX... |
tests/e2e/
├── setup.ts # E2E test environment configuration
└── predictionLifecycle.test.ts # Main lifecycle test suite
-
Test Setup (
setup.ts)- Configures testnet environment
- Sets default values
- Validates required variables
-
Lifecycle Test
- Uses real Stellar SDK clients
- Interacts with actual testnet
- Performs database operations
- Validates API responses
-
Cleanup
- Runs in
afterAllhook - Deletes test data in dependency order
- Logs cleanup operations
- Doesn't fail suite on cleanup errors
- Runs in
Each test should:
- Use unique identifiers (timestamps)
- Not depend on other tests
- Clean up its own data
- Handle cleanup failures gracefully
All operations should log:
- Operation type
- Entity IDs (marketId, userId, etc.)
- Timestamps
- Success/failure status
Example:
logger.info({ marketId, userId, amount }, "Placing prediction");Tests should:
- Expect specific error codes
- Validate error messages
- Handle testnet timeouts gracefully
- Retry on transient failures (when appropriate)
Use specific assertions:
// ✅ Good - specific
expect(market.status).toBe("resolved");
expect(claim.amount).toBeGreaterThan(prediction.amount);
// ❌ Bad - vague
expect(market).toBeTruthy();Set appropriate timeouts for testnet operations:
test("should resolve market", async () => {
// Testnet can be slow
}, 120000); // 2 minutesEach E2E run uploads:
- Test results
- Coverage reports
- Logs (if configured)
Retained for 30 days.
On scheduled run failures:
- GitHub issue created automatically
- Tagged with
e2e-failure,bug,priority - Includes run link and commit SHA
- Test execution time
- Success rate over time
- Testnet RPC latency
- Database query performance
Error: "Transaction failed: insufficient balance"
Solution:
# Fund the test account
curl "https://friendbot.stellar.org?addr=YOUR_TEST_PUBLIC_KEY"Error: "Contract CXXXXX not found"
Solution:
- Verify contract is deployed to testnet
- Check
PREDICTIFY_CONTRACT_IDis correct - Ensure contract is initialized
Error: "ECONNREFUSED ::1:5432"
Solution:
# Start PostgreSQL
docker start predictify-e2e-db
# Or create new instance
docker run -d --name predictify-e2e-db \
-e POSTGRES_DB=predictify_e2e \
-p 5432:5432 \
postgres:16-alpineError: "Timeout waiting for RPC response"
Solution:
- Stellar testnet may be under heavy load
- Retry the test
- Check testnet status: https://status.stellar.org/
- Consider increasing timeout values
Error: "Invalid signature"
Solution:
- Verify
E2E_TEST_SECRET_KEYis correct - Ensure keypair matches the registered user
- Check nonce hasn't expired
- Create test file:
tests/e2e/newScenario.test.ts - Import setup:
import '../e2e/setup' - Follow lifecycle pattern: authenticate → act → assert → cleanup
- Add to CI: Update
.github/workflows/e2e.ymlif needed
test("should handle multiple predictions on same market", async () => {
const predictions = await Promise.all([
placePrediction(marketId, "YES", "100"),
placePrediction(marketId, "NO", "50"),
placePrediction(marketId, "YES", "75"),
]);
// Verify all predictions recorded
expect(predictions).toHaveLength(3);
// Resolve market
await resolveMarket(marketId, "YES");
// Verify winners and losers
const winners = predictions.filter(p => p.outcome === "YES");
const losers = predictions.filter(p => p.outcome === "NO");
expect(winners.every(p => p.result === "won")).toBe(true);
expect(losers.every(p => p.result === "lost")).toBe(true);
});- Never commit secrets to version control
- Use environment variables or secret management tools
- Rotate test account keys periodically
- Use separate keys for CI and local development
- E2E tests use real testnet
- Data is publicly visible on-chain
- Don't use sensitive or production data
- Clean up test data after runs
- Limit access to E2E secret keys
- Use GitHub Environments for additional protection
- Audit who can trigger E2E workflows
- Review workflow logs for suspicious activity
Where possible, run independent operations in parallel:
// ✅ Parallel
const [market, user] = await Promise.all([
createMarket(),
authenticateUser(),
]);
// ❌ Sequential (slower)
const market = await createMarket();
const user = await authenticateUser();- Use transactions for multi-step operations
- Batch inserts when possible
- Index frequently queried fields
- Clean up old test data
- Testnet can be slower than mainnet
- Rate limits may apply
- Plan for occasional downtime
- Cache static data (contract ABI, etc.)
- Weekly: Review E2E test results
- Monthly: Update dependencies
- Quarterly: Audit test coverage
- Annually: Rotate test account keys
- Contract ABI changes
- New API endpoints
- Schema migrations
- Business logic changes
- Security requirements
- Jest Documentation
- Supertest Documentation
- Stellar SDK Documentation
- Soroban Documentation
- GitHub Actions Documentation
For questions or issues with E2E tests:
- Check this documentation
- Review existing GitHub issues
- Check test logs and artifacts
- Create a new issue with:
- Test output
- Environment details
- Steps to reproduce
- Expected vs actual behavior