← back to AbramsEgo

scripts/smoke.sh

65 lines

#!/bin/bash
# scripts/smoke.sh — fast (<10s) no-deps regression net for the AbramsEgo dashboard.
#
# Curls /api/data on the RUNNING instance (:9773) and asserts the contract the
# SPEC panels depend on. Run this FIRST in any audit/replenish pass instead of
# re-deriving curl checks. Exits non-zero and names the failing assertion.
#
# Env overrides: ABRAMSEGO_BASE, ABRAMSEGO_USER, ABRAMSEGO_PASS.
set -u
BASE="${ABRAMSEGO_BASE:-http://127.0.0.1:9773}"
AUSER="${ABRAMSEGO_USER:-admin}"
APASS="${ABRAMSEGO_PASS:-DW2024!}"

TMP="$(mktemp)"
trap 'rm -f "$TMP"' EXIT

CODE=$(curl -s -m 8 -o "$TMP" -w '%{http_code}' -u "$AUSER:$APASS" "$BASE/api/data") || {
  echo "FAIL http_reachable — curl could not reach $BASE/api/data"; exit 1; }
[ "$CODE" = "200" ] || { echo "FAIL http_200_with_auth — got HTTP $CODE"; exit 1; }

python3 - "$TMP" <<'PY'
import json, sys

def fail(name, detail=''):
    print(f"FAIL {name}" + (f" — {detail}" if detail else ''))
    sys.exit(1)

try:
    d = json.load(open(sys.argv[1]))
except Exception as e:
    fail('json_parse', str(e))

KEYS = ['activity', 'canaries', 'cost', 'crons', 'kamatera', 'localFleet',
        'officers', 'pnl', 'revenue', 'sessions', 'system', 'usage',
        'usageSummary', 'wins']
missing = [k for k in KEYS if k not in d]
if missing:
    fail('all_14_top_level_keys', 'missing: ' + ', '.join(missing))

jobs = (d['crons'] or {}).get('jobs') or []
if len(jobs) <= 100:
    fail('crons_length_gt_100', f'got {len(jobs)}')

# Provider COUNT is data-driven (30-day ledger window) — 4 vs 5 providers is
# normal drift, not a regression. The panel contract is: non-empty list whose
# entries carry provider+total.
prov = (d['cost'] or {}).get('byProvider') or []
if not prov:
    fail('cost_byProvider_nonempty', 'empty byProvider list')
bad = [p for p in prov if not p.get('provider') or not isinstance(p.get('total'), (int, float))]
if bad:
    fail('cost_byProvider_shape', f'{len(bad)} entries missing provider/total')

cans = (d['canaries'] or {}).get('canaries') or []
nulls = [c.get('name', '?') for c in cans if c.get('verdict') is None]
if nulls:
    fail('canaries_verdicts_nonnull', 'null verdict: ' + ', '.join(nulls))

if (d['localFleet'] or {}).get('error') is not None:
    fail('localFleet_error_null', str(d['localFleet'].get('error')))

print(f"PASS — 200 auth, 14 keys, crons {len(jobs)}, providers {len(prov)}, "
      f"canaries {len(cans)} all-verdict, localFleet clean")
PY