r3-shortage-regression.sh 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/env bash
  2. # R3-SHORTAGE-EVALUATOR-1 regression script (dev/test only, aidopdev).
  3. #
  4. # Drives the safe debug endpoint /api/aidop/s8/watch-debug/run-once twice and
  5. # verifies SHORTAGE end-to-end while ensuring TIMEOUT and OUT_OF_RANGE compat
  6. # paths do not regress:
  7. # 1. only ONE active (status != CLOSED) exception per dedup_key for SHORTAGE;
  8. # 2. last_detected_at refreshed between runs;
  9. # 3. source_payload contains target_qty / actual_qty / shortage_qty /
  10. # shortage_ratio / __ruleType / __sourceObjectType / __sourceObjectId /
  11. # __exceptionTypeCode metadata;
  12. # 4. TIMEOUT G01 still duplicate-skip against id=52;
  13. # 5. OUT_OF_RANGE G01 still duplicate-skip against id=34;
  14. # 6. baseline 13 unchanged.
  15. #
  16. # Requires the same prerequisites as r2-timeout-regression.sh (mysql client,
  17. # python3, aidopdev backend on http://localhost:5005, dev override of
  18. # WatchScheduler:DebugEndpointEnabled, populated Playwright storage state).
  19. set -euo pipefail
  20. PROJECT_DIR="${PROJECT_DIR:-/home/yy968/work/New9S/AiDOPWarehouse}"
  21. STORAGE_STATE="${PROJECT_DIR}/Web/tests/e2e/.auth/storage-state.json"
  22. BACKEND_BASE="${BACKEND_BASE:-http://localhost:5005}"
  23. TENANT_ID="${TENANT_ID:-1}"
  24. FACTORY_ID="${FACTORY_ID:-1}"
  25. DB_HOST="${DB_HOST:-123.60.180.165}"
  26. DB_PORT="${DB_PORT:-3306}"
  27. DB_NAME="${DB_NAME:-aidopdev}"
  28. DB_USER="${DB_USER:-aidopremote}"
  29. DB_PASS="${DB_PASS:-1234567890aiDOP#}"
  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='G01_TEST_SHORTAGE' AND enabled=1;")
  60. [[ "${precheck}" == "1" ]] || fail "G01_TEST_SHORTAGE not present or disabled (got '${precheck}')"
  61. ok "G01_TEST_SHORTAGE enabled"
  62. baseline=$(mysql_query "SELECT COUNT(*) FROM ado_s8_exception_type WHERE tenant_id=0 AND factory_id=0;")
  63. [[ "${baseline}" == "13" ]] || fail "baseline regression: expected 13, got ${baseline}"
  64. ok "baseline = 13"
  65. resp1="$(run_once)"
  66. count1=$(printf '%s' "${resp1}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))")
  67. [[ "${count1}" -ge 3 ]] || fail "first run-once expected at least 3 results (OOR + TIMEOUT + SHORTAGE), got ${count1}"
  68. ok "first run-once HTTP 200, count=${count1}"
  69. # OUT_OF_RANGE compat
  70. oor_ok=$(printf '%s' "${resp1}" | python3 -c "
  71. import json,sys
  72. d=json.load(sys.stdin)
  73. 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')
  74. print('YES' if hit else 'NO')
  75. ")
  76. [[ "${oor_ok}" == "YES" ]] || fail "OUT_OF_RANGE G01 regression: expected skipped duplicate id=34"
  77. ok "OUT_OF_RANGE G01 compat path: skipped duplicate id=34"
  78. # TIMEOUT compat
  79. timeout_ok=$(printf '%s' "${resp1}" | python3 -c "
  80. import json,sys
  81. d=json.load(sys.stdin)
  82. hit=any(r for r in d.get('results',[]) if str(r.get('sourceRuleId'))=='5' and r.get('skipped') and str(r.get('matchedExceptionId'))=='52')
  83. print('YES' if hit else 'NO')
  84. ")
  85. [[ "${timeout_ok}" == "YES" ]] || fail "TIMEOUT G01 regression: expected skipped duplicate id=52"
  86. ok "TIMEOUT G01: skipped duplicate id=52"
  87. before_ts=$(mysql_query "SELECT IFNULL(MAX(last_detected_at), '1970-01-01') FROM ado_s8_exception WHERE source_rule_code='G01_TEST_SHORTAGE' AND status<>'CLOSED' AND is_deleted=0;")
  88. ok "pre-second-run SHORTAGE last_detected_at=${before_ts}"
  89. sleep 1
  90. resp2="$(run_once)"
  91. count2=$(printf '%s' "${resp2}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))")
  92. ok "second run-once HTTP 200, count=${count2}"
  93. active_count=$(mysql_query "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='G01_TEST_SHORTAGE' AND status<>'CLOSED' AND is_deleted=0;")
  94. [[ "${active_count}" == "1" ]] || fail "expected exactly 1 active SHORTAGE exception, got ${active_count}"
  95. ok "active SHORTAGE exception count = 1"
  96. after_ts=$(mysql_query "SELECT MAX(last_detected_at) FROM ado_s8_exception WHERE source_rule_code='G01_TEST_SHORTAGE' AND status<>'CLOSED' AND is_deleted=0;")
  97. [[ "${after_ts}" > "${before_ts}" ]] || fail "SHORTAGE last_detected_at did not refresh"
  98. ok "SHORTAGE last_detected_at refreshed: ${before_ts} -> ${after_ts}"
  99. payload=$(mysql_query "SELECT source_payload FROM ado_s8_exception WHERE source_rule_code='G01_TEST_SHORTAGE' AND status<>'CLOSED' AND is_deleted=0 ORDER BY id DESC LIMIT 1;")
  100. for field in target_qty actual_qty shortage_qty shortage_ratio __ruleType __sourceObjectType __sourceObjectId __exceptionTypeCode; do
  101. echo "${payload}" | grep -q "${field}" || fail "source_payload missing ${field}"
  102. done
  103. ok "SHORTAGE source_payload contains target/actual/shortage/ratio + 4 metadata fields"
  104. ok "R3-SHORTAGE regression PASSED"