Skip to content

Commit 738e21e

Browse files
committed
feat(e2e): add comprehensive E2E testing infrastructure
- Add e2e-keel HTTP app with SQLite testing endpoints - Implement automated test orchestration with e2e-run script - Add query parameter support for flexible user creation - Include smoke test workflow with build → run → test → shutdown - Update documentation with E2E testing instructions - Mark SQL Spin SQLite adapter as complete in roadmap The E2E app provides HTTP endpoints for setup, user management, and transaction testing against SQLite. The automated script handles the full lifecycle including background app startup, readiness checks, and cleanup. This enables comprehensive behavioral testing of the SQL adapter implementation.
1 parent 6807faa commit 738e21e

File tree

8 files changed

+121
-5
lines changed

8 files changed

+121
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ BRAINSTORMING.md
4343

4444
*.db
4545
apps/**/.spin/logs/
46+
/apps/e2e-keel/.spin

CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,8 @@ Notes for code changes:
105105
- Keep changes minimal and focused on requested scopes.
106106
- Align with WIT contracts and layer boundaries.
107107
- Prefer adding tasks to `justfile` for repeatable workflows.
108+
109+
## Component Status (Phase 2)
110+
- `components/infrastructure/sql-spin-sqlite`: implements `sql.wit` using Spin SQLite (query/execute/transactions).
111+
- `apps/e2e-keel`: runnable HTTP app for behavioral E2E testing against SQLite.
112+
- Spin capabilities are configured in component manifests; prefer `Connection::open_default()` with a declared `default` binding.

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ just transpile
4848
just test # workspace tests (unit + BDD)
4949
# or target a single crate
5050
just test-crate sql-sqlite
51+
52+
# E2E app (HTTP): build and run
53+
just spin-build apps/e2e-keel
54+
just spin-up apps/e2e-keel
55+
56+
# Smoke tests against the running E2E app
57+
just e2e-smoke
5158
```
5259

5360
## Spin Quickstart
@@ -156,7 +163,7 @@ These options provide much of the desired operational functionality out of the b
156163
- ✅ Project structure and WIT interfaces
157164
- ✅ BDD testing framework
158165
- ✅ Spin Framework integration foundation
159-
- 🚧 SQL Spin SQLite adapter implementation
166+
- SQL Spin SQLite adapter (query/execute/transactions)
160167
- 🚧 Complete infrastructure component suite
161168

162169
**Phase 3 (Platform Services)** - Next: Spin-native platform services (observability, security, rate-limiting)
@@ -188,6 +195,13 @@ Spin helpers:
188195
- spin-cloud-login: Authenticate with Fermyon Cloud.
189196
- spin-cloud-deploy [dir='.']: Deploy a Spin app to Fermyon Cloud.
190197

198+
E2E helpers:
199+
- e2e-smoke: Runs setup, creates two users, lists users, then tests commit/rollback
200+
- e2e-setup / e2e-user / e2e-users / e2e-txn-commit / e2e-txn-rollback: Individual endpoints
201+
202+
E2E app location:
203+
- apps/e2e-keel (HTTP routes exercising SQLite flows)
204+
191205
## Community
192206

193207
- **GitHub Issues**: [Report bugs, request features, ask questions](https://github.com/marclove/keel/issues)

apps/e2e-keel/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ serde = { workspace = true }
1313
serde_json = { workspace = true }
1414
spin-sdk = "3"
1515
tracing = { workspace = true }
16-
16+
urlencoding = "2"

apps/e2e-keel/spin.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ allowed_outbound_hosts = []
1818
sqlite_databases = ["default"]
1919
environment = { SPIN_SQLITE_DB_default = "sqlite:dev.db" }
2020

21+
# spin-test integration is paused pending alignment on WIT versions
22+
# [component.e2e-api.tool.spin-test]
23+
# source = "tests-rs/target/wasm32-wasip1/release/wasm_test.wasm"
24+
# build = "(cargo component --version >/dev/null 2>&1 || cargo install cargo-component --locked) && cargo component build --release --target-dir=target"
25+
# workdir = "tests-rs"
26+
2127
[component.e2e-api.build]
2228
command = "cargo build --target wasm32-wasip2 --release"
2329
watch = ["src/**/*.rs", "Cargo.toml"]

apps/e2e-keel/src/lib.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Result;
22
use serde::{Deserialize, Serialize};
33
use spin_sdk::{http::{Method, Request, Response}, http_component, sqlite::{Connection, Value}};
4+
use urlencoding::decode;
45

56
#[derive(Serialize, Deserialize)]
67
struct ApiResponse<T> {
@@ -14,11 +15,13 @@ fn handle_e2e(req: Request) -> Result<Response> {
1415
let path = req.path_and_query().unwrap_or("/");
1516
let method = req.method();
1617
match (method, path) {
17-
(m, "/setup") if *m == Method::Post => setup(),
18+
// Allow GET for setup/txn for spin-test convenience
19+
(m, "/setup") if *m == Method::Post || *m == Method::Get => setup(),
1820
(m, "/users") if *m == Method::Post => create_user(req),
1921
(m, "/users") if *m == Method::Get => list_users(),
20-
(m, "/txn/commit") if *m == Method::Post => txn_commit(),
21-
(m, "/txn/rollback") if *m == Method::Post => txn_rollback(),
22+
(m, "/test/users-add") if *m == Method::Get => users_add_via_query(req),
23+
(m, "/txn/commit") if *m == Method::Post || *m == Method::Get => txn_commit(),
24+
(m, "/txn/rollback") if *m == Method::Post || *m == Method::Get => txn_rollback(),
2225
_ => json(404, &ApiResponse::<()> { ok: false, data: None, error: Some("not found".into()) }),
2326
}
2427
}
@@ -71,6 +74,37 @@ fn list_users() -> Result<Response> {
7174
json(200, &ApiResponse { ok: true, data: Some(users), error: None })
7275
}
7376

77+
fn users_add_via_query(req: Request) -> Result<Response> {
78+
let db = Connection::open_default()?;
79+
let qs = req.path_and_query().unwrap_or("");
80+
let mut name: Option<String> = None;
81+
let mut email: Option<String> = None;
82+
if let Some(idx) = qs.find('?') {
83+
let q = &qs[idx+1..];
84+
for pair in q.split('&') {
85+
let mut it = pair.splitn(2, '=');
86+
if let (Some(k), Some(v)) = (it.next(), it.next()) {
87+
let v = decode(v).unwrap_or_default().to_string();
88+
match k {
89+
"name" => name = Some(v),
90+
"email" => email = Some(v),
91+
_ => {}
92+
}
93+
}
94+
}
95+
}
96+
match (name, email) {
97+
(Some(n), Some(e)) => {
98+
db.execute(
99+
"INSERT INTO users (name, email) VALUES (?, ?)",
100+
&[Value::Text(n), Value::Text(e)],
101+
)?;
102+
json(200, &ApiResponse::<()> { ok: true, data: None, error: None })
103+
}
104+
_ => json(400, &ApiResponse::<()> { ok: false, data: None, error: Some("missing name or email".into()) })
105+
}
106+
}
107+
74108
fn txn_commit() -> Result<Response> {
75109
let db = Connection::open_default()?;
76110
db.execute("DELETE FROM accounts", &[])?;

justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ e2e-txn-commit:
204204
e2e-txn-rollback:
205205
curl -fsS -X POST "{{ e2e_url }}/txn/rollback"
206206

207+
# Orchestrate build → run → smoke test → shutdown, propagating exit code
208+
e2e-run:
209+
E2E_URL={{ e2e_url }} bash scripts/e2e-run.sh
210+
207211
# Create a new Spin app from a template
208212

209213
# Usage: just spin-new http-rust my-app

scripts/e2e-run.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Base URL for the app; default to Spin's default if not provided
5+
E2E_URL="${E2E_URL:-http://127.0.0.1:3000}"
6+
7+
APP_DIR="apps/e2e-keel"
8+
LOG_DIR="$APP_DIR/.spin"
9+
PID_FILE="$LOG_DIR/e2e-run.pid"
10+
LOG_FILE="$LOG_DIR/e2e-run.log"
11+
12+
echo "[e2e-run] Building app..."
13+
just spin-build "$APP_DIR"
14+
15+
echo "[e2e-run] Starting app in background (logging to $LOG_FILE)..."
16+
mkdir -p "$LOG_DIR"
17+
(
18+
cd "$APP_DIR"
19+
spin up > ".spin/e2e-run.log" 2>&1 & echo $! > ".spin/e2e-run.pid"
20+
)
21+
PID="$(cat "$APP_DIR/.spin/e2e-run.pid")"
22+
cleanup() {
23+
if kill -0 "$PID" >/dev/null 2>&1; then
24+
echo "[e2e-run] Stopping app (pid $PID)"
25+
kill "$PID" || true
26+
# Give Spin a moment to shutdown
27+
sleep 0.5 || true
28+
fi
29+
}
30+
trap cleanup EXIT
31+
32+
echo "[e2e-run] Waiting for readiness at $E2E_URL/users ..."
33+
ready=0
34+
for i in $(seq 1 60); do
35+
if curl -fsS "$E2E_URL/users" >/dev/null 2>&1; then ready=1; break; fi
36+
sleep 0.25
37+
done
38+
39+
if [ "$ready" -ne 1 ]; then
40+
echo "[e2e-run] App did not become ready. Recent log:"
41+
tail -n 100 "$APP_DIR/.spin/e2e-run.log" || true
42+
exit 1
43+
fi
44+
45+
echo "[e2e-run] Running smoke tests..."
46+
if just e2e-smoke; then
47+
rc=0
48+
else
49+
rc=$?
50+
fi
51+
52+
exit "$rc"

0 commit comments

Comments
 (0)