Skip to content

Commit a871022

Browse files
committed
test(scripts): tighten mollifier parity script with body assertions (Phase F1)
Adds per-endpoint contract checks beyond the status-only comparison: - Read endpoints assert response shape (trace.traceId present; events/attempts arrays; metadata-get { metadata, metadataType } keys; retrieve-v3 carries id + taskIdentifier + status). The result endpoint explicitly asserts 404 — its accidental-but-correct pre-Phase-A behaviour is now the locked contract. - Mutation endpoints get a read-back assertion: after PUT metadata, re-read and confirm the snapshot reflects the patch. After POST tags, retrieve and confirm runTags contains the new tag. Catches the case where the API returns 200 but the snapshot didn't actually patch. - Replay asserts the response carries a new run_-prefixed id. - New listing probe: hits /api/v1/runs and asserts the buffered runId is present in the page. Locks in Phase E's listing-merge behaviour. Script remains backwards-compatible — same exit codes, same env-var contract. Drift count now reflects shape violations alongside status divergences.
1 parent 0b989f3 commit a871022

1 file changed

Lines changed: 129 additions & 3 deletions

File tree

scripts/mollifier-api-parity.sh

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,47 @@ probe_compare() {
130130
printf "%s buffered: %s%s\n" "$c_dim" "$(body_preview "$WORK/buffered-$label.body")" "$c_reset"
131131
}
132132

133+
# assert_body LABEL JQ_FILTER EXPECTED_DESCRIPTION
134+
# Asserts the buffered response body satisfies a jq filter (returns
135+
# truthy). Use for endpoint-specific contract checks beyond status code.
136+
# E.g. for metadata-get: '. | has("metadata") and has("metadataType")'.
137+
assert_body() {
138+
local label=$1 jq_filter=$2 desc=$3
139+
local body_file=$WORK/buffered-$label.body
140+
if jq -e "$jq_filter" "$body_file" >/dev/null 2>&1; then
141+
printf "%s ✓ body shape: %s%s\n" "$c_ok" "$desc" "$c_reset"
142+
return 0
143+
fi
144+
printf "%s ✗ body shape: expected %s%s\n" "$c_fail" "$desc" "$c_reset"
145+
failures+=( "$label buffered body shape: expected $desc" )
146+
fail_count=$((fail_count + 1))
147+
return 1
148+
}
149+
150+
# assert_status_ok LABEL — buffered status must be 2xx (Phase A/C target)
151+
assert_status_ok() {
152+
local label=$1
153+
local status=$(cat "$WORK/buffered-$label.status")
154+
if [[ "$status" =~ ^2 ]]; then return 0; fi
155+
printf "%s ✗ status: expected 2xx, got %s%s\n" "$c_fail" "$status" "$c_reset"
156+
failures+=( "$label buffered status: expected 2xx, got $status" )
157+
fail_count=$((fail_count + 1))
158+
return 1
159+
}
160+
161+
# probe_buffered LABEL METHOD PATH [DATA]
162+
# Probe only the buffered run (used for follow-up read-back checks
163+
# after a mutation). Same body/status capture as probe_compare but no
164+
# parity comparison against control.
165+
probe_buffered() {
166+
local label=$1 method=$2 path=$3 data=${4:-}
167+
call "$method" "${path//\{ID\}/$BUFFERED_ID}" "buffered-$label" "$data"
168+
local status=$(cat "$WORK/buffered-$label.status")
169+
printf "%s[%-26s]%s %-6s buffered=%-3s\n" \
170+
"$c_dim" "$label" "$c_reset" "$method" "$status"
171+
printf "%s buffered: %s%s\n" "$c_dim" "$(body_preview "$WORK/buffered-$label.body")" "$c_reset"
172+
}
173+
133174
# ----------------------------------------------------------------------
134175
# 1. Set up CONTROL run — delayed trigger so it lives in PG, never executes
135176
# ----------------------------------------------------------------------
@@ -196,17 +237,102 @@ echo
196237
echo "${c_dim}==> Probing endpoints — control vs buffered should match${c_reset}"
197238
echo
198239

240+
# --- Reads --------------------------------------------------------------
241+
199242
probe_compare "retrieve-v3" GET "/api/v3/runs/{ID}"
243+
assert_status_ok "retrieve-v3"
244+
assert_body "retrieve-v3" '.id and .taskIdentifier and .status' \
245+
'id + taskIdentifier + status'
246+
200247
probe_compare "trace" GET "/api/v1/runs/{ID}/trace"
248+
assert_status_ok "trace"
249+
# Buffered run hasn't executed so the trace is a single root span +
250+
# empty events. The presenter shape: { trace: { traceId, rootSpan, events } }.
251+
assert_body "trace" '.trace and .trace.traceId' \
252+
'trace.traceId present'
253+
201254
probe_compare "events" GET "/api/v1/runs/{ID}/events"
255+
assert_status_ok "events"
256+
assert_body "events" '.events | type == "array"' \
257+
'events is an array'
258+
202259
probe_compare "attempts" GET "/api/v1/runs/{ID}/attempts"
260+
assert_status_ok "attempts"
261+
assert_body "attempts" '.attempts | type == "array" and length == 0' \
262+
'attempts is empty array'
263+
264+
# `result` is the one read endpoint that's expected to 404 (run is not
265+
# finished). Contract is { error: "Run either doesn't exist or is not
266+
# finished" } on both sides.
203267
probe_compare "result" GET "/api/v1/runs/{ID}/result"
268+
buffered_result_status=$(cat "$WORK/buffered-result.status")
269+
if [[ "$buffered_result_status" != "404" ]]; then
270+
printf "%s ✗ status: expected 404, got %s%s\n" "$c_fail" "$buffered_result_status" "$c_reset"
271+
failures+=( "result buffered status: expected 404, got $buffered_result_status" )
272+
fail_count=$((fail_count + 1))
273+
fi
274+
204275
probe_compare "metadata-get" GET "/api/v1/runs/{ID}/metadata"
205-
probe_compare "metadata-put" PUT "/api/v1/runs/{ID}/metadata" '{"metadata":{"probe":"true"}}'
206-
probe_compare "tags-add" POST "/api/v1/runs/{ID}/tags" '{"tags":["parity"]}'
276+
assert_status_ok "metadata-get"
277+
assert_body "metadata-get" 'has("metadata") and has("metadataType")' \
278+
'{ metadata, metadataType } keys present'
279+
280+
# --- Mutations + read-back ---------------------------------------------
281+
282+
probe_compare "metadata-put" PUT "/api/v1/runs/{ID}/metadata" \
283+
'{"metadata":{"probe":"true"}}'
284+
assert_status_ok "metadata-put"
285+
# Read back: the snapshot should now carry the patched metadata.
286+
probe_buffered "metadata-readback" GET "/api/v1/runs/{ID}/metadata"
287+
assert_body "metadata-readback" \
288+
'(.metadata // "") | tostring | contains("\"probe\":\"true\"")' \
289+
'snapshot metadata reflects PUT'
290+
291+
probe_compare "tags-add" POST "/api/v1/runs/{ID}/tags" \
292+
'{"tags":["parity-probe"]}'
293+
assert_status_ok "tags-add"
294+
probe_buffered "tags-readback" GET "/api/v3/runs/{ID}"
295+
assert_body "tags-readback" \
296+
'.runTags // [] | any(. == "parity-probe")' \
297+
'snapshot runTags contains "parity-probe"'
298+
299+
probe_compare "reschedule" POST "/api/v1/runs/{ID}/reschedule" \
300+
'{"delay":"5m"}'
301+
assert_status_ok "reschedule"
302+
207303
probe_compare "replay" POST "/api/v1/runs/{ID}/replay" '{}'
208-
probe_compare "reschedule" POST "/api/v1/runs/{ID}/reschedule" '{"delay":"5m"}'
304+
assert_status_ok "replay"
305+
assert_body "replay" '.id and (.id | startswith("run_"))' \
306+
'new runId returned'
307+
308+
# Cancel last — it terminates the buffered run's snapshot. Subsequent
309+
# reads on the original would still synthesise via the snapshot, but
310+
# the run is now slated for CANCELED materialisation.
209311
probe_compare "cancel-v2" POST "/api/v2/runs/{ID}/cancel" '{}'
312+
assert_status_ok "cancel-v2"
313+
314+
# --- Listing -----------------------------------------------------------
315+
316+
# Verify the buffered run surfaces in the runs list (Phase E). Pull a
317+
# generous page and assert our BUFFERED_ID is present.
318+
call GET "/api/v1/runs?page%5Bsize%5D=100" "list-buffered"
319+
list_status=$(cat "$WORK/list-buffered.status")
320+
printf "%s[%-26s]%s %-6s buffered=%-3s\n" \
321+
"$c_dim" "list-includes-buffered" "$c_reset" "GET" "$list_status"
322+
if [[ "$list_status" =~ ^2 ]]; then
323+
if jq -e --arg id "$BUFFERED_ID" '.data | any(.id == $id)' "$WORK/list-buffered.body" >/dev/null 2>&1; then
324+
printf "%s ✓ buffered runId appears in /api/v1/runs page%s\n" "$c_ok" "$c_reset"
325+
pass_count=$((pass_count + 1))
326+
else
327+
printf "%s ✗ buffered runId %s missing from /api/v1/runs page%s\n" "$c_fail" "$BUFFERED_ID" "$c_reset"
328+
failures+=( "list-includes-buffered buffered runId missing from listing" )
329+
fail_count=$((fail_count + 1))
330+
fi
331+
else
332+
printf "%s ✗ listing status: expected 2xx, got %s%s\n" "$c_fail" "$list_status" "$c_reset"
333+
failures+=( "list-includes-buffered status: expected 2xx, got $list_status" )
334+
fail_count=$((fail_count + 1))
335+
fi
210336

211337
# ----------------------------------------------------------------------
212338
# 4. Summary

0 commit comments

Comments
 (0)