From c72b3354ffee2dd2d5588d861644d1dab1d3fdd3 Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Wed, 19 Aug 2026 19:57:30 +0200 Subject: [PATCH] =?UTF-8?q?test:=20web-app-accept=20=E2=80=94=20iteration?= =?UTF-8?q?=2016's=20gate=20(Task=205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- justfile | 8 +++ scripts/web-app-accept.sh | 136 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100755 scripts/web-app-accept.sh diff --git a/justfile b/justfile index f2fb9da..7ab2f6a 100644 --- a/justfile +++ b/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. diff --git a/scripts/web-app-accept.sh b/scripts/web-app-accept.sh new file mode 100755 index 0000000..259599d --- /dev/null +++ b/scripts/web-app-accept.sh @@ -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 ]]