#!/usr/bin/env bash # R2-TIMEOUT-EVALUATOR-1/2 regression script (dev/test only, aidopdev). # # Drives the safe debug endpoint /api/aidop/s8/watch-debug/run-once twice and # verifies: # 1. only ONE active (status != CLOSED) exception per dedup_key; # 2. last_detected_at refreshed between runs; # 3. source_payload contains corrected __sourceObjectId metadata; # 4. G01_TEST_WATCH OUT_OF_RANGE compat path keeps producing duplicate skip # against id=34 (existing IN_PROGRESS exception). # # Requires: # - mysql client on PATH; # - python3 for JSON parsing; # - aidopdev backend running on http://localhost:5005; # - WatchScheduler:DebugEndpointEnabled=true (dev/test default); # - Web/tests/e2e/.auth/storage-state.json populated (run Playwright once). set -euo pipefail PROJECT_DIR="${PROJECT_DIR:-/home/yy968/work/New9S/AiDOPWarehouse}" STORAGE_STATE="${PROJECT_DIR}/Web/tests/e2e/.auth/storage-state.json" BACKEND_BASE="${BACKEND_BASE:-http://localhost:5005}" TENANT_ID="${TENANT_ID:-1}" FACTORY_ID="${FACTORY_ID:-1}" DB_HOST="${DB_HOST:-123.60.180.165}" DB_PORT="${DB_PORT:-3306}" DB_NAME="${DB_NAME:-aidopdev}" DB_USER="${DB_USER:-aidopremote}" DB_PASS="${DB_PASS:-1234567890aiDOP#}" RULE_CODE="${RULE_CODE:-G01_TEST_TIMEOUT}" fail() { echo "FAIL: $*" >&2; exit 1; } ok() { echo "OK: $*"; } [[ "${DB_NAME}" == "aidopdev" ]] || fail "DB_NAME must be aidopdev, got ${DB_NAME}" [[ -f "${STORAGE_STATE}" ]] || fail "storage state not found: ${STORAGE_STATE}" AT="$(python3 -c " import json,sys d=json.load(open(sys.argv[1])) for kv in d['origins'][0]['localStorage']: if kv['name']=='admin.net:access-token': print(kv['value'].strip('\"')); break " "${STORAGE_STATE}")" XAT="$(python3 -c " import json,sys d=json.load(open(sys.argv[1])) for kv in d['origins'][0]['localStorage']: if kv['name']=='admin.net:x-access-token': print(kv['value'].strip('\"')); break " "${STORAGE_STATE}")" [[ -n "${AT}" && "${AT}" != "null" ]] || fail "access-token missing in storage-state" [[ -n "${XAT}" && "${XAT}" != "null" ]] || fail "x-access-token missing in storage-state" mysql_query() { MYSQL_PWD="${DB_PASS}" mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" "${DB_NAME}" \ --default-character-set=utf8mb4 --connect-timeout=8 -N -B -e "$1" 2>/dev/null } run_once() { curl -fsS --max-time 15 -X POST \ -H "Authorization: Bearer ${AT}" -H "X-Access-Token: ${XAT}" \ "${BACKEND_BASE}/api/aidop/s8/watch-debug/run-once?tenantId=${TENANT_ID}&factoryId=${FACTORY_ID}" } precheck=$(mysql_query "SELECT COUNT(*) FROM ado_s8_watch_rule WHERE rule_code='${RULE_CODE}' AND enabled=1;") [[ "${precheck}" == "1" ]] || fail "${RULE_CODE} not present or disabled (got '${precheck}')" ok "${RULE_CODE} enabled" resp1="$(run_once)" count1=$(printf '%s' "${resp1}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))") [[ "${count1}" -ge 1 ]] || fail "first run-once did not return any results" ok "first run-once HTTP 200, count=${count1}" # OUT_OF_RANGE compat: G01_TEST_WATCH (sourceRuleId=1) must remain duplicate-skip against id=34. g01ok=$(printf '%s' "${resp1}" | python3 -c " import json,sys d=json.load(sys.stdin) hit=any(r for r in d.get('results',[]) if str(r.get('sourceRuleId'))=='1' and r.get('skipped') and str(r.get('matchedExceptionId'))=='34') print('YES' if hit else 'NO') ") [[ "${g01ok}" == "YES" ]] || fail "OUT_OF_RANGE G01 regression: expected skipped duplicate for sourceRuleId=1 against id=34" ok "OUT_OF_RANGE G01 compat path: skipped duplicate id=34" before_ts=$(mysql_query "SELECT IFNULL(MAX(last_detected_at), '1970-01-01') FROM ado_s8_exception WHERE source_rule_code='${RULE_CODE}' AND status<>'CLOSED' AND is_deleted=0;") ok "pre-second-run last_detected_at=${before_ts}" sleep 1 resp2="$(run_once)" count2=$(printf '%s' "${resp2}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))") ok "second run-once HTTP 200, count=${count2}" active_count=$(mysql_query "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='${RULE_CODE}' AND status<>'CLOSED' AND is_deleted=0;") [[ "${active_count}" == "1" ]] || fail "expected exactly 1 active TIMEOUT exception, got ${active_count}" ok "active TIMEOUT exception count = 1 (no duplicate creation)" after_ts=$(mysql_query "SELECT MAX(last_detected_at) FROM ado_s8_exception WHERE source_rule_code='${RULE_CODE}' AND status<>'CLOSED' AND is_deleted=0;") [[ "${after_ts}" > "${before_ts}" ]] || fail "last_detected_at did not refresh (before=${before_ts}, after=${after_ts})" ok "last_detected_at refreshed: ${before_ts} -> ${after_ts}" payload=$(mysql_query "SELECT source_payload FROM ado_s8_exception WHERE source_rule_code='${RULE_CODE}' AND status<>'CLOSED' AND is_deleted=0 ORDER BY id DESC LIMIT 1;") echo "${payload}" | grep -q '__sourceObjectId' || fail "source_payload missing __sourceObjectId metadata (R2-2 fix)" echo "${payload}" | grep -q '__sourceObjectType' || fail "source_payload missing __sourceObjectType metadata (R2-2 fix)" echo "${payload}" | grep -q '__ruleType' || fail "source_payload missing __ruleType metadata" ok "source_payload metadata fields present (__ruleType / __sourceObjectType / __sourceObjectId)" ok "R2-TIMEOUT regression PASSED"