Skip to content

Commit ab144f9

Browse files
tonyxiaoclaude
andcommitted
Add temporal e2e shell script
Standalone bash script that tests the activity flow without vitest: service (config resolution) → engine (sync execution) → Postgres. Starts engine + service on random ports, creates a sync, resolves config with include_credentials=true, calls engine /setup → /sync → verifies Postgres rows → /teardown → verifies schema dropped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
1 parent 4d6c1ae commit ab144f9

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

e2e/temporal.test.sh

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/usr/bin/env bash
2+
# End-to-end test: Service (config resolution) → Engine (sync execution)
3+
#
4+
# Tests the same flow that Temporal activities execute:
5+
# 1. Create sync via service API
6+
# 2. GET /syncs/{id}?include_credentials=true → resolved config
7+
# 3. POST /setup, /sync, /teardown on engine with X-Sync-Params
8+
# 4. Verify data in Postgres, verify teardown
9+
#
10+
# Requires:
11+
# - STRIPE_API_KEY (or .env)
12+
# - Postgres on localhost:5432 (or POSTGRES_URL)
13+
# - pnpm build must have been run
14+
set -euo pipefail
15+
16+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17+
cd "$ROOT"
18+
19+
# Load .env if present
20+
[ -f .env ] && set -a && source .env && set +a
21+
22+
: "${STRIPE_API_KEY:?Set STRIPE_API_KEY}"
23+
POSTGRES_URL="${POSTGRES_URL:-postgresql://postgres:postgres@localhost:5432/postgres}"
24+
SCHEMA="temporal_sh_$(date +%Y%m%d%H%M%S)_$$"
25+
26+
SERVICE_PORT=0
27+
ENGINE_PORT=0
28+
SERVICE_PID=""
29+
ENGINE_PID=""
30+
31+
cleanup() {
32+
echo ""
33+
echo "--- Cleanup ---"
34+
[ -n "$SERVICE_PID" ] && kill "$SERVICE_PID" 2>/dev/null && echo " Stopped service ($SERVICE_PID)"
35+
[ -n "$ENGINE_PID" ] && kill "$ENGINE_PID" 2>/dev/null && echo " Stopped engine ($ENGINE_PID)"
36+
if [ "${KEEP_TEST_DATA:-}" != "1" ]; then
37+
psql "$POSTGRES_URL" -c "DROP SCHEMA IF EXISTS \"$SCHEMA\" CASCADE" 2>/dev/null && echo " Dropped schema $SCHEMA"
38+
fi
39+
[ -n "${DATA_DIR:-}" ] && rm -rf "$DATA_DIR" && echo " Removed $DATA_DIR"
40+
}
41+
trap cleanup EXIT
42+
43+
find_free_port() {
44+
python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()'
45+
}
46+
47+
wait_for_port() {
48+
local port=$1 label=$2 timeout=${3:-30}
49+
for i in $(seq 1 "$timeout"); do
50+
if nc -z 127.0.0.1 "$port" 2>/dev/null; then
51+
echo " $label is up (port $port)"
52+
return 0
53+
fi
54+
sleep 1
55+
done
56+
echo " FAIL: $label not reachable on port $port after ${timeout}s"
57+
exit 1
58+
}
59+
60+
# --- Start engine (from its package dir so connector bins are findable) ---
61+
ENGINE_PORT=$(find_free_port)
62+
echo "Starting engine on port $ENGINE_PORT ..."
63+
(cd "$ROOT/apps/engine" && PORT=$ENGINE_PORT node dist/api/index.js) &>/dev/null &
64+
ENGINE_PID=$!
65+
wait_for_port "$ENGINE_PORT" "Engine"
66+
67+
# --- Start service (no Temporal — just config CRUD) ---
68+
SERVICE_PORT=$(find_free_port)
69+
DATA_DIR=$(mktemp -d)
70+
echo "Starting service on port $SERVICE_PORT (data: $DATA_DIR) ..."
71+
node "$ROOT/apps/service/dist/bin/cli.js" serve \
72+
--port "$SERVICE_PORT" \
73+
--data-dir "$DATA_DIR" &>/dev/null &
74+
SERVICE_PID=$!
75+
wait_for_port "$SERVICE_PORT" "Service"
76+
77+
SERVICE_URL="http://localhost:$SERVICE_PORT"
78+
ENGINE_URL="http://localhost:$ENGINE_PORT"
79+
80+
echo ""
81+
echo "=== Service → Engine E2E Test ==="
82+
echo " Service: $SERVICE_URL"
83+
echo " Engine: $ENGINE_URL"
84+
echo " Postgres: $POSTGRES_URL"
85+
echo " Schema: $SCHEMA"
86+
echo ""
87+
88+
# --- Create sync ---
89+
echo "--- 1. Create sync ---"
90+
SYNC_RESP=$(curl -sf -X POST "$SERVICE_URL/syncs" \
91+
-H 'Content-Type: application/json' \
92+
-d "{
93+
\"source\": { \"type\": \"stripe\", \"api_key\": \"$STRIPE_API_KEY\", \"backfill_limit\": 5 },
94+
\"destination\": { \"type\": \"postgres\", \"connection_string\": \"$POSTGRES_URL\", \"schema\": \"$SCHEMA\" },
95+
\"streams\": [{ \"name\": \"products\" }]
96+
}")
97+
SYNC_ID=$(echo "$SYNC_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
98+
echo " Sync: $SYNC_ID"
99+
100+
# --- Resolve config ---
101+
echo ""
102+
echo "--- 2. Resolve config (include_credentials=true) ---"
103+
RESOLVED=$(curl -sf "$SERVICE_URL/syncs/$SYNC_ID?include_credentials=true")
104+
SRC_TYPE=$(echo "$RESOLVED" | python3 -c "import sys,json; print(json.load(sys.stdin)['source']['type'])")
105+
HAS_KEY=$(echo "$RESOLVED" | python3 -c "import sys,json; print('api_key' in json.load(sys.stdin)['source'])")
106+
echo " source.type: $SRC_TYPE"
107+
echo " has api_key: $HAS_KEY"
108+
[ "$HAS_KEY" = "True" ] || { echo "FAIL: expected api_key in resolved config"; exit 1; }
109+
110+
# Build X-Sync-Params (same as what activities do)
111+
PARAMS=$(echo "$RESOLVED" | python3 -c "
112+
import sys, json
113+
c = json.load(sys.stdin)
114+
src = {k:v for k,v in c['source'].items() if k != 'type'}
115+
dst = {k:v for k,v in c['destination'].items() if k != 'type'}
116+
print(json.dumps({
117+
'source_name': c['source']['type'],
118+
'source_config': src,
119+
'destination_name': c['destination']['type'],
120+
'destination_config': dst,
121+
'streams': c.get('streams', [])
122+
}))
123+
")
124+
echo " X-Sync-Params built ($(echo "$PARAMS" | wc -c | tr -d ' ') bytes)"
125+
126+
# --- Setup ---
127+
echo ""
128+
echo "--- 3. Engine: setup ---"
129+
SETUP_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST "$ENGINE_URL/setup" \
130+
-H "X-Sync-Params: $PARAMS")
131+
echo " HTTP $SETUP_STATUS"
132+
[ "$SETUP_STATUS" = "204" ] || { echo "FAIL: expected 204, got $SETUP_STATUS"; exit 1; }
133+
134+
# --- Sync ---
135+
echo ""
136+
echo "--- 4. Engine: sync ---"
137+
SYNC_OUTPUT=$(curl -sf -X POST "$ENGINE_URL/sync" -H "X-Sync-Params: $PARAMS")
138+
LINE_COUNT=$(echo "$SYNC_OUTPUT" | wc -l | tr -d ' ')
139+
echo " NDJSON lines: $LINE_COUNT"
140+
141+
ERROR_COUNT=$(echo "$SYNC_OUTPUT" | python3 -c "
142+
import sys, json
143+
errors = 0
144+
for line in sys.stdin:
145+
line = line.strip()
146+
if not line: continue
147+
msg = json.loads(line)
148+
if msg.get('type') == 'error':
149+
errors += 1
150+
print(f' ERROR: {msg.get(\"message\", \"unknown\")}', file=sys.stderr)
151+
print(errors)
152+
")
153+
echo " Errors: $ERROR_COUNT"
154+
155+
# --- Verify Postgres ---
156+
echo ""
157+
echo "--- 5. Verify Postgres ---"
158+
ROW_COUNT=$(psql "$POSTGRES_URL" -t -c "SELECT count(*) FROM \"$SCHEMA\".\"products\"" | tr -d ' ')
159+
echo " products: $ROW_COUNT rows"
160+
[ "$ROW_COUNT" -gt 0 ] || { echo "FAIL: expected > 0 rows"; exit 1; }
161+
162+
SAMPLE=$(psql "$POSTGRES_URL" -t -c "SELECT id FROM \"$SCHEMA\".\"products\" LIMIT 1" | tr -d ' ')
163+
echo " sample: $SAMPLE"
164+
[[ "$SAMPLE" == prod_* ]] || { echo "FAIL: expected prod_ prefix, got $SAMPLE"; exit 1; }
165+
166+
# --- Teardown ---
167+
echo ""
168+
echo "--- 6. Engine: teardown ---"
169+
TEARDOWN_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST "$ENGINE_URL/teardown" \
170+
-H "X-Sync-Params: $PARAMS")
171+
echo " HTTP $TEARDOWN_STATUS"
172+
[ "$TEARDOWN_STATUS" = "204" ] || { echo "FAIL: expected 204, got $TEARDOWN_STATUS"; exit 1; }
173+
174+
TABLE_COUNT=$(psql "$POSTGRES_URL" -t -c \
175+
"SELECT count(*) FROM information_schema.tables WHERE table_schema = '$SCHEMA'" | tr -d ' ')
176+
echo " Tables remaining: $TABLE_COUNT"
177+
[ "$TABLE_COUNT" -eq 0 ] || { echo "FAIL: expected 0 tables after teardown"; exit 1; }
178+
179+
echo ""
180+
echo "=== All checks passed ==="

0 commit comments

Comments
 (0)