-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathe2e-managed.sh
More file actions
executable file
·141 lines (124 loc) · 7.58 KB
/
Copy pathe2e-managed.sh
File metadata and controls
executable file
·141 lines (124 loc) · 7.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env bash
# Full publish-to-usable e2e for a MANAGED app (Partner). Proves the whole
# chain the admin board triggers when you press "publish":
#
# POST /api/submit → the server BUILDS the keyless translation-layer adapter
# POST /admin/approve → REGISTERS the app with the broker (routable + metered)
# run the REAL built adapter → it signs with a daemon-format identity.json,
# the broker verifies the caller, injects the master key, forwards to the
# partner, meters, and enforces the per-caller rate limit.
#
# Mock partner by default. To hit the real Partner API:
# MANAGED_REAL=1 PARTNER_API_KEY=sk-... ./scripts/e2e-partner.sh
set -euo pipefail
cd "$(dirname "$0")/.."
WORK="$(mktemp -d)"
trap 'kill $(jobs -p) 2>/dev/null || true; rm -rf "$WORK"' EXIT
pass() { printf ' \033[32mPASS\033[0m %s\n' "$1"; }
fail() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; exit 1; }
MOCK=127.0.0.1:8231
BROKER=127.0.0.1:8230
PUB=127.0.0.1:8232
MASTER="MASTER-123"
echo "building pilot-app, publish-server, broker, broker-sign, ipc-call…"
go build -o "$WORK/publish-server" ./cmd/publish-server
go build -o "$WORK/broker" ./cmd/broker
go build -o "$WORK/broker-sign" ./cmd/broker-sign
go build -o "$WORK/ipc-call" ./cmd/ipc-call
# ── partner API ────────────────────────────────────────────────────────────
if [ "${MANAGED_REAL:-0}" = "1" ]; then
UPSTREAM="https://api.example.com"; MASTER="${PARTNER_API_KEY:?set PARTNER_API_KEY for real mode}"
echo "using REAL Partner at $UPSTREAM"
else
cat >"$WORK/partner.go" <<'GO'
package main
import ("net/http";"os")
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("x-api-key") != "MASTER-123" { w.WriteHeader(401); w.Write([]byte(`{"error":"bad key"}`)); return }
w.Header().Set("Content-Type","application/json")
w.Write([]byte(`{"email":"ceo@acme.com","cost_cents":2}`))
})
http.ListenAndServe(os.Getenv("ADDR"), nil)
}
GO
ADDR=$MOCK go run "$WORK/partner.go" & sleep 1
UPSTREAM="http://$MOCK"
fi
# ── 1. admin board: submit (builds the keyless adapter) ─────────────────────
ADMIN_TOKEN=dev-admin BROKER_REGISTRY="$WORK/apps.json" ALLOWED_ORIGINS="*" \
"$WORK/publish-server" -addr "$PUB" -store "$WORK/store" -key "$WORK/platform.key" & sleep 1
cat >"$WORK/submission.json" <<JSON
{
"id":"io.pilot.partner","version":"0.1.0",
"description":"Partner enrichment via the Pilot managed key.",
"email":"alex@vulturelabs.io",
"backend":{"base_url":"$UPSTREAM","auth":"managed","quota":1,"headers":[{"name":"x-api-key","value":"managed"}]},
"methods":[
{"name":"partner.find-email","description":"Find an email by domain.","latency":"med","http":{"verb":"GET","path":"/find-email"},"params":[{"name":"domain","type":"string","required":true}]},
{"name":"partner.enrich","description":"Enrich a person or company.","latency":"slow","http":{"verb":"POST","path":"/enrich"}}
],
"listing":{"display_name":"Partner","app_description":"Enrichment.","license":"MIT"},
"vendor":{"name":"Partner"}
}
JSON
resp=$(curl -s -X POST "http://$PUB/api/submit" -H 'Content-Type: application/json' --data @"$WORK/submission.json")
case_id=$(echo "$resp" | sed -n 's/.*"case_id":"\([^"]*\)".*/\1/p')
[ -n "$case_id" ] && pass "admin submit accepted (case $case_id)" || fail "submit failed: $resp"
# submit does NOT build — an admin triggers the build per case. Fire /admin/build,
# then wait for the async build to reach "pending" before approving.
CASE_JSON="$WORK/store/cases/${case_id}/case.json"
curl -s -X POST "http://$PUB/admin/build" --data-urlencode "id=$case_id" --data-urlencode "token=dev-admin" >/dev/null
st=""
for _ in $(seq 1 90); do
st=$(sed -n 's/.*"status": *"\([^"]*\)".*/\1/p' "$CASE_JSON" 2>/dev/null | head -1)
[ "$st" = "pending" ] && break
[ "$st" = "build_failed" ] && fail "async build failed: $(cat "$CASE_JSON")"
sleep 1
done
[ "$st" = "pending" ] && pass "admin build → pending" || fail "build did not reach pending (status=$st)"
# ── 2. admin board: approve (registers with the broker) ─────────────────────
# (publish trigger no-ops without a token; registration happens regardless.)
curl -s -X POST "http://$PUB/admin/approve" \
--data-urlencode "id=$case_id" --data-urlencode "guide=Search 'Partner' in the store" \
--data-urlencode "token=dev-admin" >/dev/null
grep -q '"id": "io.pilot.partner"' "$WORK/apps.json" \
&& pass "admin approve registered the app with the broker" || fail "broker registry not written: $(cat "$WORK/apps.json" 2>/dev/null)"
grep -q '"quota": 1' "$WORK/apps.json" && pass "publish-time rate limit recorded (quota 1)" || fail "quota not registered"
# ── 3. extract the REAL built adapter from the approved bundle ───────────────
# The build now emits one bundle per platform; this test runs the binary
# directly, so pick THIS host's tarball (else exec-format-errors on a
# cross-platform binary). Fall back to any tarball for old single-platform builds.
EGOOS=$(go env GOOS); EGOARCH=$(go env GOARCH)
bundle=$(find "$WORK/store" -name "*-${EGOOS}-${EGOARCH}.tar.gz" | head -1)
[ -n "$bundle" ] || bundle=$(find "$WORK/store" -name '*.tar.gz' | head -1)
[ -n "$bundle" ] || fail "no bundle produced by the admin board"
mkdir -p "$WORK/app" && tar -xzf "$bundle" -C "$WORK/app"
adapter=$(find "$WORK/app" -type f -perm -u+x -name '*-app' | head -1)
manifest=$(find "$WORK/app" -name manifest.json | head -1)
[ -n "$adapter" ] && pass "extracted the built adapter binary ($(basename "$adapter"))" || fail "no adapter binary in bundle"
grep -q 'broker.pilotprotocol.network' "$manifest" && pass "adapter manifest dials the broker, not the partner" || fail "manifest not broker-pointed"
# ── 4. boot the broker on the admin-written registry ────────────────────────
PARTNER_MASTER_KEY="$MASTER" "$WORK/broker" -registry "$WORK/apps.json" -addr "$BROKER" & sleep 1
curl -fsS "http://$BROKER/gw/health" >/dev/null && pass "broker up on the registered app" || fail "broker health failed"
# ── 5. run the real adapter; it signs with a daemon-format identity.json ─────
"$WORK/broker-sign" -gen-identity "$WORK/identity.json"
sock="$WORK/app.sock"
PARTNER_BACKEND_URL="http://$BROKER/io.pilot.partner" \
"$adapter" --socket "$sock" --manifest "$manifest" --identity "$WORK/identity.json" & sleep 1
[ -S "$sock" ] && pass "adapter spawned and serving IPC" || fail "adapter socket not up"
# 1st call: real adapter → broker (verifies signature) → partner → metered.
out=$("$WORK/ipc-call" -socket "$sock" -method partner.find-email -args '{"domain":"acme.com"}' 2>&1) || true
echo "$out" | grep -q 'ceo@acme.com' \
&& pass "managed call works end-to-end (adapter signs → broker → partner): $out" \
|| fail "first call failed: $out"
# 2nd call: same caller, quota 1 → rate-limited by the broker.
out2=$("$WORK/ipc-call" -socket "$sock" -method partner.find-email -args '{"domain":"acme.com"}' 2>&1) || true
echo "$out2" | grep -qiE 'quota|429' \
&& pass "rate limit enforced on the 2nd call ($out2)" \
|| fail "rate limit NOT enforced, got: $out2"
# 6. metering recorded for the caller.
curl -s "http://$BROKER/gw/usage" | grep -q '"calls":1' \
&& pass "broker metered exactly 1 successful call" || fail "metering wrong: $(curl -s http://$BROKER/gw/usage)"
echo
echo "partner e2e: publish → build → register → sign → broker → partner → meter + ratelimit ✓"