Skip to content

Commit e247a1d

Browse files
fullstackjamfullstackjam
authored andcommitted
feat(golden-path): add real execution golden path test
- Rename old test.sh → contract-smoke.sh (schema + API format checks) - New test.sh: full golden path with 5 steps: 1. contract-smoke (schema validation) 2. curl|bash install script fetch + syntax check + execution 3. openboot --from fixture dry-run (no TTY required) 4. openboot -u remote config fetch (dry-run, with timeout) 5. openboot doctor (no panic check) - Auto-starts mock-server.py for local runs; accepts SERVER_URL for prod - Portable timeout via _timeout() wrapper (gtimeout/timeout/no-op) - Update README with new script descriptions and usage examples
1 parent fc9d33b commit e247a1d

File tree

3 files changed

+358
-113
lines changed

3 files changed

+358
-113
lines changed

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ fixtures/ Example payloads that match the schemas
2020
snapshot-v1.json
2121
2222
golden-path/ End-to-end validation scripts
23-
test.sh Round-trip integrity + live API checks
23+
test.sh Full golden path: curl|bash + CLI dry-run + doctor (5 steps)
24+
contract-smoke.sh Schema validation + live API format checks only
2425
```
2526

2627
## How it works
@@ -33,12 +34,22 @@ golden-path/ End-to-end validation scripts
3334
## Local usage
3435

3536
```bash
36-
# Validate fixtures against schemas (needs python3 + jsonschema)
37-
pip install jsonschema
37+
# Prerequisites
38+
pip install jsonschema # for schema validation
39+
cd ../openboot && make build # build the CLI binary
40+
41+
# Full golden path (curl|bash + CLI dry-run + doctor)
42+
# Spins up a mock server automatically
3843
./golden-path/test.sh
3944

40-
# With a running server
45+
# Schema + API format checks only
46+
./golden-path/contract-smoke.sh
47+
48+
# Against a running local server
4149
SERVER_URL=http://localhost:5173 ./golden-path/test.sh
50+
51+
# Against production
52+
SERVER_URL=https://openboot.dev ./golden-path/contract-smoke.sh
4253
```
4354

4455
## Rules

golden-path/contract-smoke.sh

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/bin/bash
2+
# Golden path end-to-end test.
3+
# Validates that data survives the full CLI → Server → CLI round-trip.
4+
#
5+
# Prerequisites:
6+
# - Server running at $SERVER_URL (default: http://localhost:5173)
7+
# - CLI binary built at $CLI_BIN (default: openboot in PATH)
8+
# - jq installed
9+
#
10+
# Usage:
11+
# ./golden-path/test.sh
12+
# SERVER_URL=https://openboot.dev ./golden-path/test.sh
13+
#
14+
set -euo pipefail
15+
16+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17+
CONTRACT_DIR="$(dirname "$SCRIPT_DIR")"
18+
SERVER_URL="${SERVER_URL:-http://localhost:5173}"
19+
PASS=0
20+
FAIL=0
21+
22+
pass() { PASS=$((PASS + 1)); echo "$1"; }
23+
fail() { FAIL=$((FAIL + 1)); echo "$1: $2"; }
24+
25+
echo "Golden Path Test"
26+
echo " Server: $SERVER_URL"
27+
echo " Schemas: $CONTRACT_DIR/schemas/"
28+
echo ""
29+
30+
# --- 1. Validate fixtures against schemas ---
31+
echo "=== Schema Validation ==="
32+
33+
validate_schema() {
34+
local schema="$1" fixture="$2" label="$3"
35+
# Use ajv-cli if available, otherwise python jsonschema
36+
if command -v ajv &>/dev/null; then
37+
if ajv validate -s "$schema" -d "$fixture" --spec=draft2020 2>/dev/null; then
38+
pass "$label"
39+
else
40+
fail "$label" "schema validation failed"
41+
fi
42+
elif python3 -c "import jsonschema" 2>/dev/null; then
43+
if python3 -c "
44+
import json, jsonschema
45+
schema = json.load(open('$schema'))
46+
data = json.load(open('$fixture'))
47+
jsonschema.validate(data, schema)
48+
" 2>/dev/null; then
49+
pass "$label"
50+
else
51+
fail "$label" "schema validation failed"
52+
fi
53+
else
54+
echo " - skipped $label (install ajv-cli or python3 jsonschema)"
55+
fi
56+
}
57+
58+
validate_schema \
59+
"$CONTRACT_DIR/schemas/remote-config.json" \
60+
"$CONTRACT_DIR/fixtures/config-v1.json" \
61+
"config fixture matches schema"
62+
63+
validate_schema \
64+
"$CONTRACT_DIR/schemas/snapshot.json" \
65+
"$CONTRACT_DIR/fixtures/snapshot-v1.json" \
66+
"snapshot fixture matches schema"
67+
68+
# --- 2. Live API checks (if server is reachable) ---
69+
echo ""
70+
echo "=== Live API Checks ==="
71+
72+
if ! curl -sf "$SERVER_URL/api/health" >/dev/null 2>&1; then
73+
echo " - Server not reachable at $SERVER_URL, skipping live checks"
74+
else
75+
# /api/packages schema check
76+
PKGS_RESPONSE=$(curl -sf "$SERVER_URL/api/packages")
77+
PKGS_TMP=$(mktemp)
78+
echo "$PKGS_RESPONSE" > "$PKGS_TMP"
79+
validate_schema \
80+
"$CONTRACT_DIR/schemas/packages.json" \
81+
"$PKGS_TMP" \
82+
"/api/packages matches schema"
83+
rm -f "$PKGS_TMP"
84+
85+
# /api/packages field spot-checks
86+
if echo "$PKGS_RESPONSE" | python3 -c "
87+
import sys, json
88+
d = json.load(sys.stdin)
89+
p = d['packages']
90+
assert len(p) > 50, f'only {len(p)} packages'
91+
installers = set(x['installer'] for x in p)
92+
assert installers == {'formula','cask','npm'}, f'missing installer types: {installers}'
93+
" 2>/dev/null; then
94+
pass "/api/packages has 50+ packages with all installer types"
95+
else
96+
fail "/api/packages" "insufficient packages or missing types"
97+
fi
98+
99+
# Config endpoint check (try known public config)
100+
CONFIG_RESPONSE=$(curl -sf "$SERVER_URL/openboot/developer/config" 2>/dev/null || echo '')
101+
if [ -n "$CONFIG_RESPONSE" ] && [ "$CONFIG_RESPONSE" != "{}" ]; then
102+
CONFIG_TMP=$(mktemp)
103+
echo "$CONFIG_RESPONSE" > "$CONFIG_TMP"
104+
validate_schema \
105+
"$CONTRACT_DIR/schemas/remote-config.json" \
106+
"$CONFIG_TMP" \
107+
"live config response matches schema"
108+
rm -f "$CONFIG_TMP"
109+
else
110+
echo " - skipped config check (no public config at /openboot/developer)"
111+
fi
112+
fi
113+
114+
# --- 3. Data round-trip integrity (snapshot → config) ---
115+
echo ""
116+
echo "=== Data Round-Trip Integrity ==="
117+
118+
# The critical invariant: fields present in snapshot must survive through
119+
# server storage and config retrieval. Test with fixture data.
120+
if python3 -c "
121+
import json
122+
123+
snapshot = json.load(open('$CONTRACT_DIR/fixtures/snapshot-v1.json'))
124+
config = json.load(open('$CONTRACT_DIR/fixtures/config-v1.json'))
125+
126+
# Every formulae in snapshot should have a corresponding entry in config.packages
127+
snap_formulae = set(snapshot['packages']['formulae'])
128+
config_pkg_names = set(p['name'] for p in config['packages'])
129+
130+
# Every cask in snapshot should appear in config.casks
131+
snap_casks = set(snapshot['packages']['casks'])
132+
config_cask_names = set(p['name'] for p in config['casks'])
133+
134+
# Every npm in snapshot should appear in config.npm
135+
snap_npm = set(snapshot['packages']['npm'])
136+
config_npm_names = set(p['name'] for p in config['npm'])
137+
138+
# Config entries must have desc field (not empty for known packages)
139+
for entry in config['packages'] + config['casks'] + config['npm']:
140+
assert 'name' in entry, f'missing name in {entry}'
141+
assert 'desc' in entry, f'missing desc in {entry}'
142+
143+
# macos_prefs must survive
144+
assert len(config.get('macos_prefs', [])) > 0, 'macos_prefs lost'
145+
assert config['macos_prefs'][0]['domain'] == snapshot['macos_prefs'][0]['domain'], 'macos_prefs domain mismatch'
146+
147+
print('All round-trip invariants hold')
148+
" 2>/dev/null; then
149+
pass "fixture data round-trip invariants"
150+
else
151+
fail "round-trip" "data integrity check failed"
152+
fi
153+
154+
# --- Summary ---
155+
echo ""
156+
TOTAL=$((PASS + FAIL))
157+
echo "Results: $PASS/$TOTAL passed"
158+
if [ "$FAIL" -gt 0 ]; then
159+
echo "FAILED"
160+
exit 1
161+
else
162+
echo "ALL PASSED"
163+
fi

0 commit comments

Comments
 (0)