Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Integration Tests

on:
workflow_dispatch:
push:
paths:
- 'contracts/**'
- 'packages/**'
- 'docker/**'
- '.github/workflows/integration-tests.yml'

jobs:
integration:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build test network image
run: |
docker build -t subtrackr/stellar-standalone:test ./docker

- name: Start test network
run: |
docker-compose -f docker-compose.test.yml up -d --build

- name: Wait and run integration script
run: |
chmod +x ./scripts/test-integration.sh
./scripts/test-integration.sh
5 changes: 5 additions & 0 deletions contracts/test-helpers/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { deployContract } from '../../packages/test-harness/dist/index.js';

export async function deploy(wasmHex: string) {
return deployContract(wasmHex);
}
5 changes: 5 additions & 0 deletions contracts/test-helpers/fund.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { fundAccount } from '../../packages/test-harness/dist/index.js';

export async function fund(publicKey: string) {
return fundAccount(publicKey);
}
16 changes: 16 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3.8'
services:
stellar-standalone:
build:
context: ./docker
dockerfile: stellar-standalone.Dockerfile
image: subtrackr/stellar-standalone:test
ports:
- '8000:8000'
- '11626:11626'
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/"]
interval: 5s
timeout: 3s
retries: 12
shm_size: '64mb'
26 changes: 26 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
set -eu

echo "[entrypoint] initializing soroban standalone placeholder"

# start the placeholder soroban server
/opt/soroban/soroban-server &
SERVER_PID=$!

# simple readiness probe
echo "waiting for HTTP health on :8000"
MAX=30
COUNT=0
until curl -sSf http://localhost:8000/ >/dev/null 2>&1 || [ "$COUNT" -ge "$MAX" ]; do
sleep 1
COUNT=$((COUNT+1))
done

if [ "$COUNT" -ge "$MAX" ]; then
echo "[entrypoint] soroban server failed to start"
kill "$SERVER_PID" || true
exit 1
fi

echo "[entrypoint] soroban ready"
wait "$SERVER_PID"
17 changes: 17 additions & 0 deletions docker/stellar-standalone.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Minimal Docker image for a Soroban/Stellar standalone test network
# This image is intended for CI/local integration tests only.
FROM --platform=linux/amd64 ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y curl ca-certificates jq netcat-openbsd git && rm -rf /var/lib/apt/lists/*

# Placeholder soroban server binary - CI should replace with real soroban/stellar standalone binary
RUN mkdir -p /opt/soroban && cat > /opt/soroban/soroban-server <<'EOF'\n#!/bin/sh\necho "[soroban-standalone] starting (placeholder)"\n# simple HTTP health endpoint using netcat in background\n(while true; do echo -e "HTTP/1.1 200 OK\n\nOK" | nc -l -p 8000 -q 1; done) &\n# keep container running\nwhile sleep 3600; do :; done\nEOF\nRUN chmod +x /opt/soroban/soroban-server

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

EXPOSE 8000 11626

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
17 changes: 17 additions & 0 deletions packages/test-harness/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@subtrackr/test-harness",
"version": "0.1.0",
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json",
"test:integration": "node --enable-source-maps ./dist/runner.js"
},
"dependencies": {
"node-fetch": "^2.6.7"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
42 changes: 42 additions & 0 deletions packages/test-harness/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import fetch from 'node-fetch';

const DEFAULT_RPC = process.env.SOROBAN_RPC_URL || 'http://localhost:8000';

export async function deployContract(wasmHex: string) {
// Placeholder: submit a contract deploy to the RPC
const resp = await fetch(`${DEFAULT_RPC}/deploy`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ wasm: wasmHex })
});
return resp.json();
}

export async function fundAccount(publicKey: string) {
// Use friendbot-style endpoint if available on the standalone server
const resp = await fetch(`${DEFAULT_RPC}/friendbot?addr=${encodeURIComponent(publicKey)}`);
return resp.json();
}

export async function invokeContract(payload: any) {
const MAX_RETRIES = 2;
let attempts = 0;
while (true) {
attempts++;
const resp = await fetch(`${DEFAULT_RPC}/invoke`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload)
});
const body = await resp.json();
if (resp.ok) return body;
// simple retry on sequence number error
if (body && body.error && body.error.includes && body.error.includes('sequence')) {
if (attempts <= MAX_RETRIES) {
await new Promise((r) => setTimeout(r, 500));
continue;
}
}
throw new Error(JSON.stringify(body));
}
}
27 changes: 27 additions & 0 deletions packages/test-harness/src/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { deployContract, fundAccount, invokeContract } from './index';

async function main() {
console.log('[test-harness] runner starting');
// Placeholder runner: demonstrate API usage
try {
const acct = 'GTESTACCOUNTXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
console.log('[test-harness] funding', acct);
const fund = await fundAccount(acct);
console.log('[test-harness] funded', fund);

console.log('[test-harness] deploying contract (placeholder)');
const deploy = await deployContract('00');
console.log('[test-harness] deploy result', deploy);

console.log('[test-harness] invoking contract (placeholder)');
const inv = await invokeContract({ contract: '0x00', args: [] });
console.log('[test-harness] invoke result', inv);
} catch (err) {
console.error('[test-harness] error', err);
process.exit(2);
}

console.log('[test-harness] runner complete');
}

main();
12 changes: 12 additions & 0 deletions packages/test-harness/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"declaration": true,
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
44 changes: 44 additions & 0 deletions scripts/test-integration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail

# Integration test runner: builds the test network, snapshots, runs tests, and tears down.
ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd)
COMPOSE_FILE="$ROOT_DIR/docker-compose.test.yml"

echo "[test-integration] starting docker-compose up"
docker-compose -f "$COMPOSE_FILE" up -d --build

echo "[test-integration] waiting for service healthy"
docker-compose -f "$COMPOSE_FILE" ps

# wait for health
timeout=60
elapsed=0
until docker-compose -f "$COMPOSE_FILE" exec -T stellar-standalone sh -c "curl -sSf http://localhost:8000/ >/dev/null" >/dev/null 2>&1 || [ $elapsed -ge $timeout ]; do
sleep 1
elapsed=$((elapsed+1))
done

if [ $elapsed -ge $timeout ]; then
echo "Service failed to become healthy"
docker-compose -f "$COMPOSE_FILE" logs
exit 1
fi

export SOROBAN_RPC_URL=http://localhost:8000
export TEST_TIMEOUT_MS=30000

echo "[test-integration] running tests with SOROBAN_RPC_URL=$SOROBAN_RPC_URL"
# Run workspace tests (assumes tests are configured to use packages/test-harness helpers)
if command -v yarn >/dev/null 2>&1; then
yarn workspace @subtrackr/test-harness test:integration --runInBand || TEST_EXIT=$?
else
npm --workspace ./packages/test-harness run test:integration || TEST_EXIT=$?
fi

TEST_EXIT=${TEST_EXIT:-0}

echo "[test-integration] tearing down"
docker-compose -f "$COMPOSE_FILE" down --volumes --remove-orphans

exit $TEST_EXIT