Files
prop-ai-hr/scripts/tests/demo-check-pilot-gates.test.sh
T

173 lines
9.0 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT_DIR/scripts/demo-check.sh"
assert_contains() {
local text="$1"
grep -Fq "$text" "$SCRIPT" || {
echo "FAIL: demo-check.sh missing: $text" >&2
exit 1
}
}
assert_not_contains() {
local text="$1"
if grep -Fq "$text" "$SCRIPT"; then
echo "FAIL: demo-check.sh still contains unsafe match: $text" >&2
exit 1
fi
}
assert_contains 'AIHR_PILOT_START_DATE'
assert_contains 'AIHR_PILOT_END_DATE'
assert_contains 'AIHR_DEMO_CHECK_LIBRARY_ONLY'
assert_contains 'safe_org AS'
assert_contains 'HAVING SUM(s.session_count) >= 10'
assert_contains 'HAVING COUNT(DISTINCT ext_party_id) = 1'
assert_contains 'finished_time >='
assert_contains 'reviewed_time >='
assert_not_contains 'p.identity_key = o.ext_party_id OR p.identity_key = o.person_phone'
assert_not_contains 's.ext_party_id = o.ext_party_id OR s.ext_party_id = o.person_phone'
if ! command -v docker >/dev/null || ! docker ps --format '{{.Names}}' | grep -qx 'wygj-mysql'; then
echo "SKIP: behavioral pilot gate test requires wygj-mysql"
exit 0
fi
export AIHR_DEMO_CHECK_LIBRARY_ONLY=true
# shellcheck source=../demo-check.sh
source "$SCRIPT"
unset AIHR_PILOT_DB
[[ "$(pilot_sql_scalar 'SELECT 1')" == "1" ]] || {
echo "FAIL: default ry-vue pilot database is unavailable" >&2
exit 1
}
TEST_DB="wygj_demo_check_test_$$"
cleanup() {
docker exec wygj-mysql mysql -uroot -proot -e "DROP DATABASE IF EXISTS \`$TEST_DB\`" >/dev/null 2>&1 || true
}
trap cleanup EXIT
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 <<SQL >/dev/null
CREATE DATABASE \`$TEST_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE \`$TEST_DB\`;
CREATE TABLE aihr_practice_scenario (tenant_id varchar(20), position varchar(80), enabled tinyint);
CREATE TABLE aihr_knowledge_attach (tenant_id varchar(20), type varchar(50), status tinyint);
CREATE TABLE aihr_case_record (
tenant_id varchar(20), status varchar(30), title varchar(120), transcript text, summary varchar(1000)
);
CREATE TABLE aihr_practice_session (tenant_id varchar(20), session_id varchar(100), ext_party_id varchar(100), mode varchar(30), finished_time datetime, satisfaction_score tinyint);
CREATE TABLE aihr_practice_assignment (tenant_id varchar(20), ext_party_id varchar(100), create_time datetime);
CREATE TABLE aihr_org_snapshot (
tenant_id varchar(20), employment_status varchar(20), ext_party_id varchar(100),
project_code varchar(50), person_phone varchar(30)
);
CREATE TABLE aihr_practice_calibration (
id bigint auto_increment primary key, tenant_id varchar(20), session_id varchar(100),
original_score int, corrected_score int, create_time datetime
);
CREATE TABLE aihr_sop_answer_review (
tenant_id varchar(20), status varchar(30), usable tinyint,
reviewed_time datetime, create_time datetime
);
INSERT INTO aihr_practice_scenario
SELECT '000000','生活顾问',1 FROM (
SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
) n;
INSERT INTO aihr_knowledge_attach
SELECT '000000','sop',2 FROM (
SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
) n;
INSERT INTO aihr_knowledge_attach VALUES
('000000','training',2),
('other','sop',2),
('000000','sop',1);
INSERT INTO aihr_case_record
SELECT '000000','已入库', CONCAT('case-', n), '已按流程回访,手机号1390****1111', '已完成' FROM (
SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10
UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL SELECT 20
) n;
INSERT INTO aihr_case_record VALUES
('000000','已转写','pending','',''),
('other','已入库','other','','');
INSERT INTO aihr_org_snapshot VALUES
('000000','active','A','P1','13900000001'),
('000000','active','B','P1','13900000001'),
('000000','active','C','P2','D'),
('000000','active','D','P2','13900000004'),
('000000','active','E','P3','13900000005'),
('000000','active','F','P3','13900000006');
INSERT INTO aihr_practice_assignment VALUES
('000000','D','2026-07-08 08:00:00'),
('000000','13900000001','2026-07-08 08:00:00'),
('000000','E','2026-07-08 08:00:00'),
('000000','13900000005','2026-07-08 08:00:00'),
('000000','F','2026-07-08 08:00:00');
INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time)
SELECT '000000', CONCAT('e-', n), CASE WHEN n <= 5 THEN 'E' ELSE '13900000005' END, 'mobile', '2026-07-08 09:00:00'
FROM (
SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
) n;
INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time)
SELECT '000000', CONCAT('f-', n), 'F','mobile','2026-07-08 10:00:00' FROM (
SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10
) n;
INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time)
VALUES
('000000','e-out','E','mobile','2026-07-06 10:00:00'),
('000000','unsafe-inside','D','mobile','2026-07-08 10:00:00');
UPDATE aihr_practice_session SET satisfaction_score = 5 WHERE finished_time >= '2026-07-07 00:00:00';
INSERT INTO aihr_practice_calibration (tenant_id,session_id,original_score,corrected_score,create_time) VALUES
('000000','f-1',80,82,'2026-07-08 11:00:00'),
('000000','unsafe-inside',80,40,'2026-07-08 11:00:00'),
('000000','e-out',80,40,'2026-07-06 11:00:00');
INSERT INTO aihr_sop_answer_review VALUES
('000000','已评审',1,'2026-07-08 12:00:00','2026-07-08 11:00:00'),
('000000','已评审',0,'2026-07-06 12:00:00','2026-07-06 11:00:00'),
('000000','待评审',NULL,NULL,'2026-07-08 13:00:00'),
('000000','待评审',NULL,NULL,'2026-07-06 13:00:00');
SQL
export AIHR_PILOT_DB="$TEST_DB"
pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
[[ "$pilot_output" == *'ten_sessions=1/5'* ]] || { echo "FAIL: 9/10 boundary or raw window count: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'org_login=2/6'* ]] || { echo "FAIL: unsafe org identities were not excluded: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'org_pilot=2/20'* ]] || { echo "FAIL: collision or dual-key identity was miscounted: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'projects=1/1-2'* ]] || { echo "FAIL: safe pilot project count: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'org_ten_sessions=1/2'* ]] || { echo "FAIL: formal 9/10 boundary: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'calibration=1/20 consistency=1/1'* ]] || { echo "FAIL: calibration window: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'sop_docs=5/5 stored_cases=20/20'* ]] || { echo "FAIL: BRD content quantity gates: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'case_unmasked=0'* ]] || { echo "FAIL: redacted case content gate: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'sop_usable=1/1 sop_pending=1'* ]] || { echo "FAIL: SOP window: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'satisfaction=5.0/5 responses=19'* ]] || { echo "FAIL: safe satisfaction identity scope: $pilot_output" >&2; exit 1; }
pilot_sql_scalar "INSERT INTO aihr_practice_session (tenant_id, session_id, ext_party_id, mode, finished_time, satisfaction_score) VALUES ('000000','e-10','13900000005','mobile','2026-07-09 09:00:00',5)" >/dev/null
pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
[[ "$pilot_output" == *'org_ten_sessions=2/2'* ]] || { echo "FAIL: formal 10/10 boundary: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'satisfaction=5.0/5 responses=20'* ]] || { echo "FAIL: formal satisfaction scope after window update: $pilot_output" >&2; exit 1; }
pilot_sql_scalar "UPDATE aihr_case_record SET transcript = '手机号13900000000' WHERE tenant_id = '000000' AND status = '已入库' LIMIT 1" >/dev/null
pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
[[ "$pilot_output" == *'case_unmasked=1'* ]] || { echo "FAIL: unmasked case PII was not detected: $pilot_output" >&2; exit 1; }
if (AIHR_PILOT_STRICT=true AIHR_PILOT_START_DATE= AIHR_PILOT_END_DATE= check_pilot_samples) >/tmp/wygj-pilot-strict-test.log 2>&1; then
echo "FAIL: strict mode accepted an implicit date window" >&2
exit 1
fi
grep -Fq 'strict pilot check requires AIHR_PILOT_START_DATE and AIHR_PILOT_END_DATE' /tmp/wygj-pilot-strict-test.log || {
echo "FAIL: strict mode missing-date error is unclear" >&2
exit 1
}
echo "PASS: demo pilot gates use a bounded window and unique formal identities"