This document details the setup and configuration of the comprehensive integration test suite for testing the Stellar Goal Vault campaign lifecycle state transitions.
┌─────────────────────────────────────────┐
│ Integration Test Suite │
├─────────────────────────────────────────┤
│ │
│ Test Worker 1 Test Worker 2 │
│ ├─ /tmp/test-1.db ├─ /tmp/test-2.db
│ └─ Express Server └─ Express Server
│ │
│ Test Worker 3 Test Worker 4 │
│ ├─ /tmp/test-3.db └─ /tmp/test-4.db
│ └─ Express Server └─ Express Server
│ │
└─────────────────────────────────────────┘
Key Features:
- Process-Level Isolation: Each test worker runs in its own process (Vitest default)
- Unique Database Paths: Use
{PID}-{TIMESTAMP}to guarantee unique databases - Automatic Cleanup: Temporary database files are cleaned up after tests
- No Lock Contention: File-based databases don't lock across processes
- Random Port Assignment: Express server uses
PORT=0for random available port
{
environment: "node", // Node.js environment
threads: true, // Use worker threads (default)
maxThreads: 4, // Run up to 4 tests in parallel
minThreads: 1, // Minimum 1 thread
isolate: true, // Isolate each test file
globals: true, // globals dont need to be imported
testTimeout: 30000, // 30 second timeout per test
}- Flow: Create → 3 Pledges → Reach Target → Claim
- Verifications:
- Campaign progresses through states correctly
- Pledges accumulate and update campaign totals
- Events are recorded in chronological order with correct metadata
- Claim updates campaign state and records event
- Double Claim: Verify idempotency and rejection of second claim
- Insufficient Funding: Test claim prevention without reaching target
- Premature Claim: Test claim prevention before deadline
- Refund After Claim: Verify refunds blocked for claimed campaigns
- Failed Campaign Refunds: Test refund flow for campaigns below target
- Invalid Contributors: Reject refunds from users who didn't pledge
- Double Refund: Prevent refunding same contributor twice
- Unauthorized Claims: Non-creator attempts to claim
- Field Validation: Missing/invalid required fields
- Pledge Constraints: Negative/zero amounts rejected
- Non-existent Campaigns: All operations reject on invalid ID
- State Integrity: Verify all state changes are consistent
- Event Ordering: Timestamps and sequence verification
- Campaign Independence: Multiple campaigns in parallel don't interfere
- Health Endpoint: Services report status correctly
- Concurrent Requests: 5 simultaneous campaign creations succeed
Total: ~15 test suites, 70+ assertions, 100% state machine coverage
The test suite automatically handles cleanup:
- Pre-test Setup:
beforeAll()hook creates isolated database - Test Execution: Tests run in complete isolation
- Post-test Cleanup:
afterAll()hook removes temporary database file - No File Lockups: File-based SQLite doesn't create lock files in temp cleanup
// Create unique test database path using PID and timestamp
const TEST_DB_PATH = path.join(
"/tmp",
`stellar-goal-vault-integration-${process.pid}-${Date.now()}.db`
);
// Set environment variable before importing app
process.env.DB_PATH = TEST_DB_PATH;
// After all tests complete
afterAll(async () => {
return new Promise<void>((resolve) => {
server.close(() => {
fs.rmSync(TEST_DB_PATH, { force: true }); // Delete temp database
resolve();
});
});
});- WAL Mode Enabled: Better concurrent access
- Foreign Keys Enabled: Data integrity
- Direct SQL: No ORM overhead
- Average Operation Time: < 50ms per operation
Scenario 1: Sequential (baseline)
├─ Test 1 [████] 500ms
├─ Test 2 [████] 500ms
├─ Test 3 [████] 500ms
└─ Test 4 [████] 500ms
Total: 2000ms
Scenario 2: Parallel (4 threads)
├─ Test 1 [████]
├─ Test 2 [████] — All run simultaneously
├─ Test 3 [████]
└─ Test 4 [████]
Total: 500ms (4x faster!)
- Individual Test Suite: 100-500ms
- Full Suite (15 tests): < 10 seconds
- Startup/Teardown: < 1 second per worker
- Database Access: < 50ms per query
The test suite uses explicit environment variable checks to prevent accidental production database usage:
// Located at the very top of integration_test.ts
process.env.DB_PATH = TEST_DB_PATH; // Temporary only
process.env.CONTRACT_ID = ""; // Disable blockchain
process.env.PORT = "0"; // Random port only// Production DB would be (example):
process.env.DB_PATH = "/var/lib/stellar-goal-vault/campaigns.db"
// Test DB is (guaranteed):
/tmp/stellar-goal-vault-integration-{PID}-{TIMESTAMP}.db
// These paths are completely different
// → Cannot accidentally write to production- ✅ Test database always in
/tmp/(temporary filesystem) - ✅ Test database filename includes
PIDandTIMESTAMP - ✅ Test database automatically deleted after tests
- ✅ No environment variables point to production files
- ✅ Express server uses random port (PORT=0)
- ✅ Contract ID is empty string (disables blockchain)
- ✅ No network calls to production Soroban
See .github/workflows/backend-integration-tests.yml for full workflow:
- Run on: [push to main/develop, pull_requests]
- Test Matrix: Node 18.x, 20.x
- Parallel Jobs: 2 simultaneous Node versions
- Automated Cleanup: Built-in to workflow
- Coverage: Automatically uploaded to Codecov
- Failure Notifications: Automatic via GitHubGitHub Actions Runner
├─ Node 18.x Matrix Job
│ └─ npm test (4 Vitest threads)
│ ├─ Test Worker 1 → /tmp/test-1.db
│ ├─ Test Worker 2 → /tmp/test-2.db
│ ├─ Test Worker 3 → /tmp/test-3.db
│ └─ Test Worker 4 → /tmp/test-4.db
│
└─ Node 20.x Matrix Job
└─ npm test (4 Vitest threads)
├─ Test Worker 1 → /tmp/test-5.db
├─ Test Worker 2 → /tmp/test-6.db
├─ Test Worker 3 → /tmp/test-7.db
└─ Test Worker 4 → /tmp/test-8.db
All 8 worker processes run independently with unique databases.
backend/
├── tests/
│ ├── integration_test.ts # Main integration test suite (770+ lines)
│ │ ├── Setup & Configuration
│ │ ├── Test Utilities
│ │ ├── Happy Path Tests
│ │ ├── Edge Case Tests
│ │ ├── Authorization Tests
│ │ ├── State Consistency Tests
│ │ └── Health & Stability Tests
│ ├── utils.ts # Shared test helpers (220+ lines)
│ │ ├── Mock Fixtures
│ │ ├── API Request Helpers
│ │ ├── Assertion Helpers
│ │ └── Utility Functions
│ └── README.md # Test documentation
├── vitest.config.ts # Vitest configuration
├── src/
│ ├── index.ts # Express server
│ ├── index.test.ts # API unit tests
│ └── services/
│ ├── campaignStore.ts # Campaign business logic
│ ├── campaignStore.test.ts # Unit tests
│ └── ...other services...
└── package.json
# Run all tests (unit + integration)
npm test
# Run only integration tests
npm test -- tests/
# Run only specific test file
npm test -- tests/integration_test.ts
# Run in watch mode (re-run on file changes)
npm test -- --watch
# Run with coverage report
npm test -- --coverage
# Run with debug output
npm test -- --reporter=verbose# Run with custom timeout (60 seconds)
npm test -- --testTimeout 60000
# Run single thread (debug mode)
npm test -- --maxThreads 1
# Run specific test by name
npm test -- --reporter=verbose -t "Happy Path"
# Enable Chrome DevTools debugging
npm test -- --inspect --inspect-brk# Set by integration_test.ts automatically
DB_PATH=/tmp/stellar-goal-vault-integration-{PID}-{TIMESTAMP}.db
CONTRACT_ID="" # Empty to disable blockchain
PORT=0 # Random available port
NODE_ENV=test # OptionalALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org:443
NETWORK_PASSPHRASE=Test SDF Network ; September 2015
CONTRACT_AMOUNT_DECIMALS=2Cause: Port 3000 is already in use
Solution: Tests use PORT=0 (random), so this shouldn't happen. If it does:
lsof -i :3000
kill -9 <PID>Cause: Dependencies not installed
Solution:
cd backend
npm installCause: Multiple processes accessing same database
Solution: This shouldn't happen due to unique path strategy. Verify:
ls -la /tmp/stellar-goal-vault-*.db
# Should show different PIDs/timestampsCause: Slow system or server not starting Solution:
npm test -- --testTimeout 60000 --maxThreads 2Cause: Tests interrupted before cleanup
Solution:
rm /tmp/stellar-goal-vault-*.dbPre-defined test data ensures consistency:
MOCK_CREATORS = {
alice: "GAAA...",
bob: "GBBB...",
}
MOCK_CONTRIBUTORS = {
dave: "GDDD...",
eve: "GEEE...",
}
MOCK_ASSETS = {
USDC: "USDC",
XLM: "XLM",
}// Standard test campaign
{
creator: CREATOR_1,
title: "Test Campaign",
description: "A test campaign",
assetCode: "USDC",
targetAmount: 1000,
deadline: nowInSeconds() + 86400,
}
// Can be customized for specific tests
createTestCampaign({
targetAmount: 500,
deadline: nowInSeconds() + 50, // Short deadline
})- Add test function to appropriate describe block
- Use utilities from
tests/utils.ts - Follow naming conventions:
test("should ...") - Use assertions for all state changes
- No hard-coded IDs (use fixtures)
- Identify affected test in
integration_test.ts - Update assertions if API behavior changed
- Verify edge cases still covered
- Run full test suite locally:
npm test - Push for CI/CD verification
Enable verbose output and breakpoints:
npm test -- --reporter=verbose --inspect-brk
# Then open chrome://inspect in Chrome