r2-timeout-regression.sh 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. #!/usr/bin/env bash
  2. # R2-TIMEOUT-EVALUATOR-1/2 regression script (dev/test only, aidopdev).
  3. #
  4. # Drives the safe debug endpoint /api/aidop/s8/watch-debug/run-once twice and
  5. # verifies:
  6. # 1. only ONE active (status != CLOSED) exception per dedup_key;
  7. # 2. last_detected_at refreshed between runs;
  8. # 3. source_payload contains corrected __sourceObjectId metadata;
  9. # 4. G01_TEST_WATCH OUT_OF_RANGE compat path keeps producing duplicate skip
  10. # against id=34 (existing IN_PROGRESS exception).
  11. #
  12. # Requires:
  13. # - mysql client on PATH;
  14. # - python3 for JSON parsing;
  15. # - aidopdev backend running on http://localhost:5005;
  16. # - WatchScheduler:DebugEndpointEnabled=true (dev/test default);
  17. # - Web/tests/e2e/.auth/storage-state.json populated (run Playwright once).
  18. set -euo pipefail
  19. PROJECT_DIR="${PROJECT_DIR:-/home/yy968/work/New9S/AiDOPWarehouse}"
  20. STORAGE_STATE="${PROJECT_DIR}/Web/tests/e2e/.auth/storage-state.json"
  21. BACKEND_BASE="${BACKEND_BASE:-http://localhost:5005}"
  22. TENANT_ID="${TENANT_ID:-1}"
  23. FACTORY_ID="${FACTORY_ID:-1}"
  24. DB_HOST="${DB_HOST:-123.60.180.165}"
  25. DB_PORT="${DB_PORT:-3306}"
  26. DB_NAME="${DB_NAME:-aidopdev}"
  27. DB_USER="${DB_USER:-aidopremote}"
  28. DB_PASS="${DB_PASS:-1234567890aiDOP#}"
  29. RULE_CODE="${RULE_CODE:-G01_TEST_TIMEOUT}"
  30. fail() { echo "FAIL: $*" >&2; exit 1; }
  31. ok() { echo "OK: $*"; }
  32. [[ "${DB_NAME}" == "aidopdev" ]] || fail "DB_NAME must be aidopdev, got ${DB_NAME}"
  33. [[ -f "${STORAGE_STATE}" ]] || fail "storage state not found: ${STORAGE_STATE}"
  34. AT="$(python3 -c "
  35. import json,sys
  36. d=json.load(open(sys.argv[1]))
  37. for kv in d['origins'][0]['localStorage']:
  38. if kv['name']=='admin.net:access-token':
  39. print(kv['value'].strip('\"')); break
  40. " "${STORAGE_STATE}")"
  41. XAT="$(python3 -c "
  42. import json,sys
  43. d=json.load(open(sys.argv[1]))
  44. for kv in d['origins'][0]['localStorage']:
  45. if kv['name']=='admin.net:x-access-token':
  46. print(kv['value'].strip('\"')); break
  47. " "${STORAGE_STATE}")"
  48. [[ -n "${AT}" && "${AT}" != "null" ]] || fail "access-token missing in storage-state"
  49. [[ -n "${XAT}" && "${XAT}" != "null" ]] || fail "x-access-token missing in storage-state"
  50. mysql_query() {
  51. MYSQL_PWD="${DB_PASS}" mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" "${DB_NAME}" \
  52. --default-character-set=utf8mb4 --connect-timeout=8 -N -B -e "$1" 2>/dev/null
  53. }
  54. run_once() {
  55. curl -fsS --max-time 15 -X POST \
  56. -H "Authorization: Bearer ${AT}" -H "X-Access-Token: ${XAT}" \
  57. "${BACKEND_BASE}/api/aidop/s8/watch-debug/run-once?tenantId=${TENANT_ID}&factoryId=${FACTORY_ID}"
  58. }
  59. precheck=$(mysql_query "SELECT COUNT(*) FROM ado_s8_watch_rule WHERE rule_code='${RULE_CODE}' AND enabled=1;")
  60. [[ "${precheck}" == "1" ]] || fail "${RULE_CODE} not present or disabled (got '${precheck}')"
  61. ok "${RULE_CODE} enabled"
  62. resp1="$(run_once)"
  63. count1=$(printf '%s' "${resp1}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))")
  64. [[ "${count1}" -ge 1 ]] || fail "first run-once did not return any results"
  65. ok "first run-once HTTP 200, count=${count1}"
  66. # OUT_OF_RANGE compat: G01_TEST_WATCH (sourceRuleId=1) must remain duplicate-skip against id=34.
  67. g01ok=$(printf '%s' "${resp1}" | python3 -c "
  68. import json,sys
  69. d=json.load(sys.stdin)
  70. 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')
  71. print('YES' if hit else 'NO')
  72. ")
  73. [[ "${g01ok}" == "YES" ]] || fail "OUT_OF_RANGE G01 regression: expected skipped duplicate for sourceRuleId=1 against id=34"
  74. ok "OUT_OF_RANGE G01 compat path: skipped duplicate id=34"
  75. 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;")
  76. ok "pre-second-run last_detected_at=${before_ts}"
  77. sleep 1
  78. resp2="$(run_once)"
  79. count2=$(printf '%s' "${resp2}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))")
  80. ok "second run-once HTTP 200, count=${count2}"
  81. active_count=$(mysql_query "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='${RULE_CODE}' AND status<>'CLOSED' AND is_deleted=0;")
  82. [[ "${active_count}" == "1" ]] || fail "expected exactly 1 active TIMEOUT exception, got ${active_count}"
  83. ok "active TIMEOUT exception count = 1 (no duplicate creation)"
  84. 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;")
  85. [[ "${after_ts}" > "${before_ts}" ]] || fail "last_detected_at did not refresh (before=${before_ts}, after=${after_ts})"
  86. ok "last_detected_at refreshed: ${before_ts} -> ${after_ts}"
  87. 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;")
  88. echo "${payload}" | grep -q '__sourceObjectId' || fail "source_payload missing __sourceObjectId metadata (R2-2 fix)"
  89. echo "${payload}" | grep -q '__sourceObjectType' || fail "source_payload missing __sourceObjectType metadata (R2-2 fix)"
  90. echo "${payload}" | grep -q '__ruleType' || fail "source_payload missing __ruleType metadata"
  91. ok "source_payload metadata fields present (__ruleType / __sourceObjectType / __sourceObjectId)"
  92. ok "R2-TIMEOUT regression PASSED"