r3-out-of-range-regression.sh 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. #!/usr/bin/env bash
  2. # R3-OUT_OF_RANGE regression script — S8-REGRESSION-FIXTURE-1 重构版(dev/test only, aidopdev)。
  3. #
  4. # 演变史:
  5. # - 旧版基于 G01_TEST_WATCH 旧 fixture(id=34)+ 强依赖 baseline=13 + id=34 backfill 断言。
  6. # - 重构后:默认复用 demo rule 11 DEMO_ORDER_DIMENSION_OOR(CTO 拍板二.B),
  7. # 若 demo rule 缺失则 record_skip。rule 12 (DEMO_ORDER_YIELD_OOR) 保留为演示备份不动。
  8. #
  9. # 验证项(demo rule 11 模式):
  10. # 1. 双 run-once HTTP 200;
  11. # 2. OUT_OF_RANGE 主链已建单;活动 exception 数与 dedup_key 1:1;
  12. # 3. last_detected_at 在两次运行之间推进;
  13. # 4. source_payload 含 measured_value/upper_bound/deviation/direction + __ 元数据;
  14. # 5. demo rule 11 守恒。
  15. set -uo pipefail
  16. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  17. # shellcheck source=./s8-regression-common.sh
  18. source "${SCRIPT_DIR}/s8-regression-common.sh"
  19. auth_load
  20. RULE_CODE="${RULE_CODE:-DEMO_ORDER_DIMENSION_OOR}"
  21. baseline_before=$(read_baseline)
  22. echo "==== r3-out-of-range-regression RULE_CODE=${RULE_CODE} baseline_before=${baseline_before} ===="
  23. if ! require_demo_rule "${RULE_CODE}"; then
  24. record_skip "OUT_OF_RANGE fixture '${RULE_CODE}' not present or disabled — historic G01_TEST_WATCH removed from dev"
  25. print_summary
  26. exit 0
  27. fi
  28. record_pass "${RULE_CODE} enabled"
  29. RULE_ID=$(get_rule_id_by_code "${RULE_CODE}")
  30. snap_enabled=$(get_rule_field "${RULE_ID}" enabled)
  31. snap_paused=$(get_rule_field "${RULE_ID}" paused_until)
  32. snap_trigger=$(get_rule_field "${RULE_ID}" trigger_count_required)
  33. snap_recover=$(get_rule_field "${RULE_ID}" recover_count_required)
  34. snap_rtype=$(get_rule_field "${RULE_ID}" rule_type)
  35. snap_expr=$(get_rule_field "${RULE_ID}" expression)
  36. snap_params=$(get_rule_field "${RULE_ID}" params_json)
  37. resp1=$(run_once_endpoint)
  38. count1=$(printf '%s' "${resp1}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))")
  39. [[ "${count1}" -ge 1 ]] && record_pass "first run-once HTTP 200, count=${count1}" || record_fail "first run-once empty (${count1})"
  40. failed1=$(printf '%s' "${resp1}" | python3 -c "
  41. import json,sys
  42. d=json.load(sys.stdin)
  43. print(sum(1 for r in d.get('results',[]) if r.get('errorMessage') or r.get('reason')=='create_failed'))
  44. ")
  45. [[ "${failed1}" == "0" ]] && record_pass "first run-once failed=0" || record_fail "first run-once failed=${failed1}"
  46. before_ts=$(mysql_run "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;")
  47. echo "pre-second-run last_detected_at=${before_ts}"
  48. sleep 1
  49. resp2=$(run_once_endpoint)
  50. count2=$(printf '%s' "${resp2}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))")
  51. [[ "${count2}" -ge 1 ]] && record_pass "second run-once HTTP 200, count=${count2}" || record_fail "second run-once empty (${count2})"
  52. # 活动 exception 数 = 数据源 dedup_key 行数(dimension_oor 命中 OOR-DEMO-001/002 两条)
  53. oor_active=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='${RULE_CODE}' AND status<>'CLOSED' AND is_deleted=0;")
  54. [[ "${oor_active}" -ge 1 ]] && record_pass "active OUT_OF_RANGE exception count=${oor_active} (>=1)" || record_fail "expected at least 1 active OOR exception, got ${oor_active}"
  55. after_ts=$(mysql_run "SELECT MAX(last_detected_at) FROM ado_s8_exception WHERE source_rule_code='${RULE_CODE}' AND status<>'CLOSED' AND is_deleted=0;")
  56. [[ "${after_ts}" > "${before_ts}" ]] && record_pass "last_detected_at refreshed: ${before_ts} -> ${after_ts}" || record_fail "last_detected_at did not refresh (before=${before_ts}, after=${after_ts})"
  57. payload=$(mysql_run "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;")
  58. metadata_ok=1
  59. for field in measured_value upper_bound deviation direction __ruleType __sourceObjectType __sourceObjectId; do
  60. echo "${payload}" | grep -q "${field}" || { record_fail "source_payload missing ${field}"; metadata_ok=0; }
  61. done
  62. (( metadata_ok == 1 )) && record_pass "source_payload contains measured/upper/deviation/direction + 3 metadata fields"
  63. final_enabled=$(get_rule_field "${RULE_ID}" enabled)
  64. final_paused=$(get_rule_field "${RULE_ID}" paused_until)
  65. final_trigger=$(get_rule_field "${RULE_ID}" trigger_count_required)
  66. final_recover=$(get_rule_field "${RULE_ID}" recover_count_required)
  67. final_rtype=$(get_rule_field "${RULE_ID}" rule_type)
  68. final_expr=$(get_rule_field "${RULE_ID}" expression)
  69. final_params=$(get_rule_field "${RULE_ID}" params_json)
  70. [[ "${final_enabled}" == "${snap_enabled}" && "${final_paused}" == "${snap_paused}" \
  71. && "${final_trigger}" == "${snap_trigger}" && "${final_recover}" == "${snap_recover}" \
  72. && "${final_rtype}" == "${snap_rtype}" && "${final_expr}" == "${snap_expr}" \
  73. && "${final_params}" == "${snap_params}" ]] \
  74. && record_pass "${RULE_CODE} 守恒: enabled/paused/trigger/recover/rule_type/expression/params_json 全部不变" \
  75. || record_fail "${RULE_CODE} 守恒失败: 配置字段被污染"
  76. # rule 12 (YIELD_OOR) 备份不动断言
  77. r12_state=$(mysql_run "SELECT CONCAT_WS(':', enabled, IFNULL(paused_until,'NULL'), trigger_count_required, recover_count_required) FROM ado_s8_watch_rule WHERE id=12;")
  78. [[ "${r12_state}" == "1:NULL:1:1" ]] && record_pass "demo rule 12 (YIELD_OOR) untouched: ${r12_state}" || record_fail "demo rule 12 drift: ${r12_state}"
  79. assert_baseline_unchanged "${baseline_before}"
  80. print_summary
  81. exit_by_summary