| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- #!/usr/bin/env bash
- # R6-2-DETECTION-LOG-EDGE-CASES-1 edge regression script (dev/test only, aidopdev).
- #
- # Drives detection_log NO_HIT and EVALUATE_FAILED edge cases that the
- # baseline three-class regression does not naturally exercise:
- # 1. G01_TEST_TIMEOUT_NO_HIT — TIMEOUT rule that returns rows whose
- # due_at is in the future, so evaluator succeeds with hits.Count==0;
- # 2. G01_TEST_TIMEOUT_FAILED — TIMEOUT rule with params_json missing
- # dueAtField, so evaluator throws S8RuleEvaluatorException
- # (reason=params_schema_invalid).
- #
- # Idempotent: rules are upserted with INSERT ... ON DUPLICATE KEY UPDATE
- # semantics via SELECT-then-INSERT/UPDATE; rules are left enabled=0 after
- # verification (kept in DB for audit replay). Detection_log entries are
- # not cleaned. id=34/52/53 must remain unaffected.
- 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#}"
- NO_HIT_RULE='G01_TEST_TIMEOUT_NO_HIT'
- FAILED_RULE='G01_TEST_TIMEOUT_FAILED'
- NO_HIT_PARAMS='{"dueAtField":"due_at","statusField":"status","completedStates":["CLOSED","DONE","COMPLETED"],"objectCodeField":"related_object_code","objectIdField":"source_object_id","graceMinutes":0,"exceptionTypeCode":"DELIVERY_DELAY"}'
- NO_HIT_EXPR="SELECT 'ORDER-NO-HIT-01' AS related_object_code, 'ORDER-NO-HIT-01' AS source_object_id, DATE_ADD(NOW(), INTERVAL 1 HOUR) AS due_at, 'PENDING' AS status"
- # FAILED rule: deliberately omit dueAtField so evaluator throws params_schema_invalid.
- FAILED_PARAMS='{"statusField":"status","completedStates":["CLOSED","DONE","COMPLETED"],"objectCodeField":"related_object_code","objectIdField":"source_object_id","graceMinutes":0,"exceptionTypeCode":"DELIVERY_DELAY"}'
- FAILED_EXPR="SELECT 'ORDER-FAILED-01' AS related_object_code, 'ORDER-FAILED-01' AS source_object_id, DATE_SUB(NOW(), INTERVAL 1 HOUR) AS due_at, 'PENDING' AS status"
- 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_run() {
- 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 30 -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}"
- }
- # Idempotent upsert by rule_code: ensure enabled=1 with the desired expression+params for this run.
- upsert_rule() {
- local code="$1"; local expr="$2"; local params="$3"
- local exists
- exists=$(mysql_run "SELECT COUNT(*) FROM ado_s8_watch_rule WHERE rule_code='${code}' AND tenant_id=${TENANT_ID} AND factory_id=${FACTORY_ID};")
- if [[ "${exists}" == "0" ]]; then
- mysql_run "INSERT INTO ado_s8_watch_rule
- (tenant_id, factory_id, rule_code, scene_code, data_source_id, watch_object_type, expression, severity, poll_interval_seconds, enabled, created_at, rule_type, source_object_type, params_json)
- VALUES (${TENANT_ID}, ${FACTORY_ID}, '${code}', 'S1S7_DELIVERY', 1, 'ORDER', \"${expr}\", 'HIGH', 60, 1, NOW(), 'TIMEOUT', 'ORDER', '${params}');" >/dev/null
- ok "upsert_rule INSERT ${code}"
- else
- mysql_run "UPDATE ado_s8_watch_rule SET expression=\"${expr}\", params_json='${params}', enabled=1, updated_at=NOW() WHERE rule_code='${code}' AND tenant_id=${TENANT_ID} AND factory_id=${FACTORY_ID};" >/dev/null
- ok "upsert_rule UPDATE ${code}"
- fi
- }
- baseline=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception_type WHERE tenant_id=0 AND factory_id=0;")
- [[ "${baseline}" == "13" ]] || fail "baseline regression: expected 13, got ${baseline}"
- ok "baseline = 13"
- # Pre-condition: id=34/52/53 must be the only active samples and not recovered.
- oor_active=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='G01_TEST_WATCH' AND status<>'CLOSED' AND is_deleted=0;")
- to_active=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='G01_TEST_TIMEOUT' AND status<>'CLOSED' AND is_deleted=0;")
- sh_active=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='G01_TEST_SHORTAGE' AND status<>'CLOSED' AND is_deleted=0;")
- [[ "${oor_active}" == "1" ]] || fail "pre-OUT_OF_RANGE active count expected 1, got ${oor_active}"
- [[ "${to_active}" == "1" ]] || fail "pre-TIMEOUT active count expected 1, got ${to_active}"
- [[ "${sh_active}" == "1" ]] || fail "pre-SHORTAGE active count expected 1, got ${sh_active}"
- ok "pre-active counts: OUT_OF_RANGE=1 / TIMEOUT=1 / SHORTAGE=1"
- upsert_rule "${NO_HIT_RULE}" "${NO_HIT_EXPR}" "${NO_HIT_PARAMS}"
- upsert_rule "${FAILED_RULE}" "${FAILED_EXPR}" "${FAILED_PARAMS}"
- # Marker timestamp so subsequent log queries scope to this run only.
- marker=$(mysql_run "SELECT NOW();")
- sleep 1
- resp="$(run_once)"
- count=$(printf '%s' "${resp}" | python3 -c "import json,sys;print(json.load(sys.stdin).get('count',0))")
- ok "run-once HTTP 200, count=${count}"
- # 1. NO_HIT log present, no exception created.
- nohit_logs=$(mysql_run "SELECT COUNT(*) FROM ado_s8_detection_log WHERE rule_code='${NO_HIT_RULE}' AND detect_result='NO_HIT' AND detected_at >= '${marker}';")
- [[ "${nohit_logs}" -ge 1 ]] || fail "expected at least 1 NO_HIT log for ${NO_HIT_RULE}, got ${nohit_logs}"
- nohit_other=$(mysql_run "SELECT COUNT(*) FROM ado_s8_detection_log WHERE rule_code='${NO_HIT_RULE}' AND detect_result IN ('CREATED','RECOVERED','REFRESHED') AND detected_at >= '${marker}';")
- [[ "${nohit_other}" == "0" ]] || fail "${NO_HIT_RULE} should not produce CREATED/RECOVERED/REFRESHED in this run, got ${nohit_other}"
- nohit_excs=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='${NO_HIT_RULE}' AND is_deleted=0;")
- [[ "${nohit_excs}" == "0" ]] || fail "${NO_HIT_RULE} must not create exceptions, got ${nohit_excs}"
- ok "NO_HIT log present (${nohit_logs}) / no CREATED/REFRESHED/RECOVERED / no exception"
- # 2. EVALUATE_FAILED log present with reason+message, no exception created.
- failed_logs=$(mysql_run "SELECT COUNT(*) FROM ado_s8_detection_log WHERE rule_code='${FAILED_RULE}' AND detect_result='EVALUATE_FAILED' AND detected_at >= '${marker}';")
- [[ "${failed_logs}" -ge 1 ]] || fail "expected at least 1 EVALUATE_FAILED log for ${FAILED_RULE}, got ${failed_logs}"
- failed_reason=$(mysql_run "SELECT failure_reason FROM ado_s8_detection_log WHERE rule_code='${FAILED_RULE}' AND detect_result='EVALUATE_FAILED' AND detected_at >= '${marker}' ORDER BY id DESC LIMIT 1;")
- [[ -n "${failed_reason}" && "${failed_reason}" != "NULL" ]] || fail "${FAILED_RULE} EVALUATE_FAILED missing failure_reason"
- failed_msg=$(mysql_run "SELECT CHAR_LENGTH(IFNULL(failure_message,'')) FROM ado_s8_detection_log WHERE rule_code='${FAILED_RULE}' AND detect_result='EVALUATE_FAILED' AND detected_at >= '${marker}' ORDER BY id DESC LIMIT 1;")
- [[ "${failed_msg}" -gt 0 ]] || fail "${FAILED_RULE} EVALUATE_FAILED missing failure_message"
- failed_excs=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE source_rule_code='${FAILED_RULE}' AND is_deleted=0;")
- [[ "${failed_excs}" == "0" ]] || fail "${FAILED_RULE} must not create exceptions, got ${failed_excs}"
- ok "EVALUATE_FAILED log present (${failed_logs}) / failure_reason='${failed_reason}' / message non-empty / no exception"
- # 3. Main three-class samples must still be REFRESHED in this run, and untouched (not recovered).
- for code in G01_TEST_WATCH G01_TEST_TIMEOUT G01_TEST_SHORTAGE; do
- cnt=$(mysql_run "SELECT COUNT(*) FROM ado_s8_detection_log WHERE rule_code='${code}' AND detect_result='REFRESHED' AND detected_at >= '${marker}';")
- [[ "${cnt}" -ge 1 ]] || fail "${code}: expected REFRESHED log in this run, got ${cnt}"
- done
- ok "main three-class samples each produced REFRESHED in this run (not blocked by FAILED)"
- main_recovered=$(mysql_run "SELECT COUNT(*) FROM ado_s8_exception WHERE id IN (34,52,53) AND recovered_at IS NOT NULL;")
- [[ "${main_recovered}" == "0" ]] || fail "id=34/52/53 must NOT be marked recovered, got ${main_recovered}"
- ok "id=34/52/53 recovered_at remain NULL"
- main_status=$(mysql_run "SELECT GROUP_CONCAT(CONCAT(id,':',status) ORDER BY id) FROM ado_s8_exception WHERE id IN (34,52,53);")
- [[ "${main_status}" == "34:IN_PROGRESS,52:NEW,53:NEW" ]] || fail "id=34/52/53 status drift: ${main_status}"
- ok "id=34/52/53 status unchanged: ${main_status}"
- # Cleanup: disable the two edge rules but keep them in DB for audit replay.
- mysql_run "UPDATE ado_s8_watch_rule SET enabled=0, updated_at=NOW() WHERE rule_code IN ('${NO_HIT_RULE}','${FAILED_RULE}') AND tenant_id=${TENANT_ID} AND factory_id=${FACTORY_ID};" >/dev/null
- ok "disabled edge rules ${NO_HIT_RULE} / ${FAILED_RULE}"
- ok "R6 detection_log edge regression PASSED"
|