test: web-app-accept — iteration 16's gate (Task 5)
scripts/web-app-accept.sh: 14 checks, network-free — temp git remote from the framework dir, file:// substituted into a temp app copy, then fetch + wo.lock + build; 401-without-token; empty list; 201 create; 409 duplicate (@unique); 400 malformed json; list/show payloads; 404; 201 order (FK); 409 delete-while-referenced with the server surviving; pipelined keep-alive (2 responses, 1 connection); SIGTERM; WAL restart persistence. Wired as `just web-app`. 14/0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
24c991069d
commit
c72b3354ff
2 changed files with 144 additions and 0 deletions
8
justfile
8
justfile
|
|
@ -35,6 +35,14 @@ dist:
|
|||
deps-accept:
|
||||
./scripts/deps-accept.sh
|
||||
|
||||
# web-app: iteration 16's gate — the whole chain at run time, network-free:
|
||||
# a temp git remote from docs/examples/writeonce-framework, file:// URL
|
||||
# substituted into a temp copy of docs/examples/web-app, then fetch -> lock
|
||||
# -> build -> serve -> the storefront matrix (auth/CRUD/@unique/FK/404/400/
|
||||
# pipelining) -> SIGTERM -> WAL restart persistence.
|
||||
web-app:
|
||||
./scripts/web-app-accept.sh
|
||||
|
||||
# install-accept: extract the dist tarball to a temp prefix, PATH it, and prove
|
||||
# `woc version` + a from-scratch project build+run (self-located wovm) + the
|
||||
# wo-constraint refusal all work — the "tarball install actually works" gate.
|
||||
|
|
|
|||
136
scripts/web-app-accept.sh
Executable file
136
scripts/web-app-accept.sh
Executable file
|
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/env bash
|
||||
# scripts/web-app-accept.sh — iteration 16's acceptance gate. Proves the whole
|
||||
# chain at run time, network-free: a temp git remote is built from
|
||||
# docs/examples/writeonce-framework, its file:// URL is substituted into a
|
||||
# temp copy of docs/examples/web-app, then: fetch -> lock -> build -> serve ->
|
||||
# the storefront matrix -> SIGTERM -> restart persistence. The repo itself
|
||||
# never carries .wo-deps/wo.lock artifacts.
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WOC="$ROOT/compiler/_build/default/bin/woc"
|
||||
WOVM="$ROOT/runtime/wovm"
|
||||
PORT="${WA_PORT:-18801}"
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
ok() { echo "ok $1"; pass=$((pass + 1)); }
|
||||
bad() { echo "FAIL $1 -- $2"; fail=$((fail + 1)); }
|
||||
|
||||
if [[ ! -x "$WOC" || ! -x "$WOVM" ]]; then
|
||||
echo "web-app-accept: build woc and wovm first (just woc-build; just wovm-build)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
W="$(mktemp -d "${TMPDIR:-/tmp}/web-app-accept.XXXXXX")"
|
||||
SRV=""
|
||||
cleanup() {
|
||||
[[ -n "$SRV" ]] && kill -9 "$SRV" 2>/dev/null
|
||||
[[ -n "${WA_ACCEPT_KEEP:-}" ]] && echo "kept $W" || rm -rf "$W"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ---- the framework as a git remote; the app pointed at it ----
|
||||
cp -r "$ROOT/docs/examples/writeonce-framework" "$W/fw"
|
||||
git -C "$W/fw" init -q
|
||||
git -C "$W/fw" add -A
|
||||
git -C "$W/fw" -c user.email=t@t -c user.name=t commit -qm v01
|
||||
git -C "$W/fw" tag v0.1.0
|
||||
cp -r "$ROOT/docs/examples/web-app" "$W/app"
|
||||
sed -i "s|https://github.com/shoneyj/writeonce-framework|file://$W/fw|" "$W/app/wo.toml"
|
||||
printf '[build]\nruntime = "%s"\n' "$WOVM" >> "$W/app/wo.toml"
|
||||
|
||||
# ---- 1. fetch + lock + build ----
|
||||
if "$WOC" "$W/app" >"$W/build.out" 2>&1 && [[ -x "$W/app/target/web-app" && -f "$W/app/wo.lock" ]]; then
|
||||
ok "deps chain: fetch + wo.lock + build"
|
||||
else
|
||||
bad "build" "$(head -1 "$W/build.out")"
|
||||
printf 'web-app-accept: %d checks, %d failures\n' "$((pass + fail))" "$fail"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DATA="$W/data"; mkdir -p "$DATA"
|
||||
WA_TOKEN=s3cr3t WO_DATA="$DATA" "$W/app/target/web-app" "$PORT" >"$W/srv.out" 2>&1 &
|
||||
SRV=$!
|
||||
for _ in $(seq 1 40); do grep -q listening "$W/srv.out" 2>/dev/null && break; sleep 0.1; done
|
||||
|
||||
# one tiny HTTP client; python is already a repo test dependency
|
||||
hit() { # method path [body] [auth: yes|no] -> "STATUS|BODY"
|
||||
python3 - "$PORT" "$1" "$2" "${3:-}" "${4:-yes}" <<'PYEOF'
|
||||
import socket, sys
|
||||
port, method, path, body, auth = int(sys.argv[1]), sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
|
||||
s = socket.create_connection(("127.0.0.1", port), timeout=5)
|
||||
h = f"{method} {path} HTTP/1.1\r\nhost: a\r\n"
|
||||
if auth == "yes": h += "authorization: Bearer s3cr3t\r\n"
|
||||
h += f"content-length: {len(body)}\r\n\r\n{body}"
|
||||
s.sendall(h.encode())
|
||||
d = b""
|
||||
while b"\r\n\r\n" not in d: d += s.recv(2000)
|
||||
head, _, rest = d.partition(b"\r\n\r\n")
|
||||
want = int([l for l in head.decode().splitlines() if l.lower().startswith("content-length")][0].split(":")[1])
|
||||
while len(rest) < want: rest += s.recv(2000)
|
||||
s.close()
|
||||
print(head.decode().splitlines()[0].split(" ")[1] + "|" + rest.decode())
|
||||
PYEOF
|
||||
}
|
||||
|
||||
expect() { # name got want-status [want-body-substring]
|
||||
local name="$1" got="$2" ws="$3" wb="${4:-}"
|
||||
local st="${got%%|*}" body="${got#*|}"
|
||||
if [[ "$st" == "$ws" && ( -z "$wb" || "$body" == *"$wb"* ) ]]; then
|
||||
ok "$name"
|
||||
else
|
||||
bad "$name" "got $st body=$body"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- 2..10 the storefront matrix ----
|
||||
expect "401 without the token" "$(hit GET /products '' no)" 401
|
||||
expect "empty list" "$(hit GET /products)" 200 "[]"
|
||||
expect "create product (201)" "$(hit POST /products '{"name":"mug","price":900,"stock":5}')" 201 '"name":"mug"'
|
||||
expect "duplicate name is 409 (@unique)" "$(hit POST /products '{"name":"mug","price":1,"stock":1}')" 409
|
||||
expect "malformed json is 400" "$(hit POST /products '{oops')" 400
|
||||
expect "list shows the product" "$(hit GET /products)" 200 '"price":900'
|
||||
expect "show by :name capture" "$(hit GET /products/mug)" 200 '"stock":5'
|
||||
expect "unknown product is 404" "$(hit GET /products/none)" 404
|
||||
expect "create order (FK)" "$(hit POST /orders '{"product":"mug","qty":2}')" 201
|
||||
expect "delete restricted by FK (409), server survives" "$(hit DELETE /products/mug)" 409 "reference"
|
||||
|
||||
# ---- 11. pipelined keep-alive: two requests, one connection ----
|
||||
n="$(timeout 5 python3 - "$PORT" <<'PYEOF'
|
||||
import socket, sys
|
||||
port = int(sys.argv[1])
|
||||
s = socket.create_connection(("127.0.0.1", port), timeout=3)
|
||||
r = b"GET /products HTTP/1.1\r\nhost: a\r\nauthorization: Bearer s3cr3t\r\ncontent-length: 0\r\n\r\n"
|
||||
s.sendall(r + r)
|
||||
buf = b""
|
||||
try:
|
||||
while buf.count(b"HTTP/1.1") < 2:
|
||||
got = s.recv(4000)
|
||||
if not got: break
|
||||
buf += got
|
||||
except socket.timeout:
|
||||
pass
|
||||
print(buf.count(b"HTTP/1.1"))
|
||||
PYEOF
|
||||
)"
|
||||
[[ "$n" == "2" ]] && ok "pipelined keep-alive (2 responses, 1 connection)" \
|
||||
|| bad "pipelining" "expected 2 responses, got $n"
|
||||
|
||||
# ---- 12. SIGTERM stops it ----
|
||||
kill -TERM "$SRV"
|
||||
stopped=1
|
||||
for _ in $(seq 1 30); do kill -0 "$SRV" 2>/dev/null || { stopped=0; break; }; sleep 0.1; done
|
||||
[[ $stopped -eq 0 ]] && ok "SIGTERM stops the server" || bad "stop" "still running"
|
||||
SRV=""
|
||||
|
||||
# ---- 13. restart persistence (WAL replay) ----
|
||||
WA_TOKEN=s3cr3t WO_DATA="$DATA" "$W/app/target/web-app" "$PORT" >>"$W/srv.out" 2>&1 &
|
||||
SRV=$!
|
||||
sleep 0.5
|
||||
expect "product survives a restart (WAL)" "$(hit GET /products)" 200 '"name":"mug"'
|
||||
kill -TERM "$SRV" 2>/dev/null; SRV=""
|
||||
|
||||
echo
|
||||
printf 'web-app-accept: %d checks, %d failures\n' "$((pass + fail))" "$fail"
|
||||
[[ $fail -eq 0 ]]
|
||||
Loading…
Reference in a new issue