#!/usr/bin/env bash # API smoke tests — chạy trên server đang lắng nghe. set -euo pipefail # shellcheck source=../lib/common.sh source "$(dirname "$0")/../lib/common.sh" BASE="${1:-http://127.0.0.1:${LM_TEST_PORT}}" MISSION_ID="${TEST_MISSION_ID:-}" RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' PASS=0 FAIL=0 log_pass() { echo -e "${GREEN}PASS${NC} $*"; PASS=$((PASS + 1)); } log_fail() { echo -e "${RED}FAIL${NC} $*"; FAIL=$((FAIL + 1)); } json_field() { python3 - "$1" "$2" <<'PY' import json, sys doc = json.loads(open(sys.argv[1]).read()) path = sys.argv[2].split(".") cur = doc for p in path: cur = cur[p] print(cur) PY } http_code() { curl -s -o "$1" -w '%{http_code}' "${@:2}" } assert_code() { local name="$1" expect="$2" file="$3" shift 3 local code code="$(http_code "$file" "$@")" if [[ "$code" == "$expect" ]]; then log_pass "$name (HTTP $code)" else log_fail "$name — expected HTTP $expect, got $code" [[ -f "$file" ]] && head -c 400 "$file" >&2 || true echo >&2 fi } assert_json_true() { local name="$1" file="$2" expr="$3" if python3 - "$file" "$expr" <<'PY' import json, sys doc = json.loads(open(sys.argv[1]).read()) env = {"doc": doc, "any": any, "all": all, "len": len, "list": list, "isinstance": isinstance} ok = eval(sys.argv[2], {"__builtins__": {}}, env) sys.exit(0 if ok else 1) PY then log_pass "$name" else log_fail "$name" head -c 400 "$file" >&2 || true echo >&2 fi } TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT echo "API smoke tests → $BASE" echo # --- Health & static --- assert_code "GET /api/health" 200 "$TMP/health.json" -X GET "$BASE/api/health" assert_json_true "health ok" "$TMP/health.json" 'doc.get("ok") is True' assert_code "GET /" 200 "$TMP/index.html" -X GET "$BASE/" assert_code "GET /missions.js" 200 "$TMP/missions.js" -X GET "$BASE/missions.js" assert_code "GET /api/state" 200 "$TMP/state.json" -X GET "$BASE/api/state" assert_code "GET /api/missions" 200 "$TMP/missions.json" -X GET "$BASE/api/missions" if [[ -z "$MISSION_ID" ]]; then MISSION_ID="$(python3 -c " import json doc = json.load(open('$TMP/missions.json')) missions = doc.get('missions', []) preferred = 'testmission00001' ids = [m.get('id') for m in missions if isinstance(m, dict)] if preferred in ids: print(preferred) elif ids: print(ids[0]) ")" fi if [[ -z "$MISSION_ID" ]]; then log_fail "no mission available for queue tests" exit 1 fi echo "Mission id: $MISSION_ID" echo assert_json_true "missions available" "$TMP/missions.json" 'len(doc.get("missions", [])) >= 1' # --- Queue pause/continue (chạy sớm, trước các test enqueue khác) --- curl -s -X DELETE "$BASE/api/mission_queue" -o /dev/null || true curl -s -X POST "$BASE/api/mission_queue" \ -H 'Content-Type: application/json' \ -d "{\"mission_id\":\"$MISSION_ID\"}" -o "$TMP/qpost.json" for _ in $(seq 1 30); do curl -s "$BASE/api/mission_queue" -o "$TMP/runner_poll.json" RUNNER_STATE="$(python3 -c "import json; print(json.load(open('$TMP/runner_poll.json')).get('runner',{}).get('state',''))")" if [[ "$RUNNER_STATE" == "running" ]]; then break fi sleep 0.2 done if [[ "$RUNNER_STATE" == "running" ]]; then assert_code "POST /api/mission_queue/pause" 200 "$TMP/pause.json" \ -X POST "$BASE/api/mission_queue/pause" assert_json_true "runner paused" "$TMP/pause.json" 'doc.get("state") == "paused"' assert_code "POST /api/mission_queue/continue" 200 "$TMP/cont.json" \ -X POST "$BASE/api/mission_queue/continue" assert_json_true "runner not paused" "$TMP/cont.json" 'doc.get("state") != "paused"' else log_fail "runner never reached running (pause test skipped)" fi # --- LiDAR CRUD --- assert_code "POST /api/lidars" 201 "$TMP/lidar.json" \ -X POST "$BASE/api/lidars" \ -H 'Content-Type: application/json' \ -d '{"name":"smoke-lidar","ip":"192.168.99.1","port":2112}' LIDAR_ID="$(json_field "$TMP/lidar.json" id 2>/dev/null || echo "")" if [[ -n "$LIDAR_ID" ]]; then log_pass "lidar created id=$LIDAR_ID" PASS=$((PASS + 1)) assert_code "PUT /api/lidars" 200 "$TMP/lidar_put.json" \ -X PUT "$BASE/api/lidars/$LIDAR_ID" \ -H 'Content-Type: application/json' \ -d '{"name":"smoke-lidar","ip":"192.168.99.2","port":2112}' assert_code "DELETE /api/lidars" 204 "$TMP/lidar_del.txt" \ -X DELETE "$BASE/api/lidars/$LIDAR_ID" else log_fail "lidar create — no id in response" fi # --- IMU CRUD --- assert_code "POST /api/imus" 201 "$TMP/imu.json" \ -X POST "$BASE/api/imus" \ -H 'Content-Type: application/json' \ -d '{"name":"smoke-imu","frame_id":"imu_smoke","topic":"/imu/smoke","source":"external"}' IMU_ID="$(json_field "$TMP/imu.json" id 2>/dev/null || echo "")" if [[ -n "$IMU_ID" ]]; then log_pass "imu created id=$IMU_ID" PASS=$((PASS + 1)) assert_code "DELETE /api/imus" 204 "$TMP/imu_del.txt" -X DELETE "$BASE/api/imus/$IMU_ID" else log_fail "imu create — no id in response" fi # --- Clear queue --- curl -s -X DELETE "$BASE/api/mission_queue" -o /dev/null || true curl -s -X DELETE "$BASE/api/v2.0.0/mission_queue" -o /dev/null || true # --- MiR v2 enqueue (Cách C REST) --- assert_code "POST /api/v2.0.0/mission_queue" 201 "$TMP/v2q.json" \ -X POST "$BASE/api/v2.0.0/mission_queue" \ -H 'Content-Type: application/json' \ -d "{\"mission_id\":\"$MISSION_ID\",\"priority\":2,\"robot_id\":\"default\"}" assert_json_true "v2 queue entry mission_id" "$TMP/v2q.json" \ 'doc.get("mission_id") == "'"$MISSION_ID"'"' assert_code "GET /api/v2.0.0/mission_queue" 200 "$TMP/v2list.json" -X GET "$BASE/api/v2.0.0/mission_queue" for _ in $(seq 1 15); do curl -s "$BASE/api/v2.0.0/mission_queue" -o "$TMP/v2list.json" if python3 -c "import json; d=json.load(open('$TMP/v2list.json')); exit(0 if isinstance(d,list) and len(d)>=1 else 1)" 2>/dev/null; then break fi sleep 0.2 done assert_json_true "v2 queue non-empty" "$TMP/v2list.json" 'isinstance(doc, list) and len(doc) >= 1' assert_code "GET /api/v2.0.0/status" 200 "$TMP/v2status.json" -X GET "$BASE/api/v2.0.0/status" assert_json_true "v2 status has state_text" "$TMP/v2status.json" '"state_text" in doc' # --- Modbus trigger (Cách C) --- assert_code "POST /api/triggers" 201 "$TMP/trig.json" \ -X POST "$BASE/api/triggers" \ -H 'Content-Type: application/json' \ -d "{\"name\":\"smoke-trigger\",\"coil_id\":1001,\"mission_id\":\"$MISSION_ID\"}" TRIG_ID="$(json_field "$TMP/trig.json" id 2>/dev/null || echo "")" if [[ -n "$TRIG_ID" ]]; then log_pass "trigger created id=$TRIG_ID" PASS=$((PASS + 1)) assert_code "POST modbus fire coil" 200 "$TMP/fire.json" \ -X POST "$BASE/api/modbus/coils/1001/trigger" assert_code "DELETE /api/triggers" 204 "$TMP/trig_del.txt" \ -X DELETE "$BASE/api/triggers/$TRIG_ID" else log_fail "trigger create — no id" fi # --- Fleet schedule --- assert_code "POST /api/fleet/schedules" 201 "$TMP/sched.json" \ -X POST "$BASE/api/fleet/schedules" \ -H 'Content-Type: application/json' \ -d "{\"name\":\"smoke-schedule\",\"mission_id\":\"$MISSION_ID\",\"start_mode\":\"asap\",\"priority\":1}" SCHED_ID="$(json_field "$TMP/sched.json" id 2>/dev/null || echo "")" if [[ -n "$SCHED_ID" ]]; then log_pass "schedule created id=$SCHED_ID" PASS=$((PASS + 1)) assert_code "DELETE /api/fleet/schedules" 204 "$TMP/sched_del.txt" \ -X DELETE "$BASE/api/fleet/schedules/$SCHED_ID" else log_fail "schedule create — no id" fi assert_code "GET /api/fleet/robots" 200 "$TMP/robots.json" -X GET "$BASE/api/fleet/robots" assert_json_true "robots list" "$TMP/robots.json" 'isinstance(doc, list) and len(doc) >= 1' echo echo "Results: $PASS passed, $FAIL failed" if [[ "$FAIL" -gt 0 ]]; then exit 1 fi