@@ -0,0 +1,323 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" 默认测试套件:奖助学金评定逻辑防伪校验。
设计原则:
- 仅用标准库(不依赖 pytest / jinja2 / requests / pandas) , CI 与本地均能直接 `python tests/run_tests.py` 跑
- 不依赖 sqlite3( v1.0.1 骨架尚未引入 DB schema, 全部测试在纯函数层)
- 覆盖: util.constants 版本对齐 + SKILL.md 元数据对齐 + CLI 入口 dispatch
- 退出码: 0 = 全部通过, 1 = 有失败
- -v 开关:详细输出每个用例的状态
release.ps1 在打 tag 前会自动跑 `python tests/run_tests.py -v`,任何一个
test_* 返回 (False, msg) 即 abort 发布。
"""
from __future__ import annotations
import argparse
import io
import re
import sys
from contextlib import redirect_stdout
from pathlib import Path
from typing import Any , Callable
# 让 scripts/ 下的模块可导入
_REPO_ROOT = Path ( __file__ ) . resolve ( ) . parent . parent
_SCRIPTS_DIR = _REPO_ROOT / " scripts "
sys . path . insert ( 0 , str ( _SCRIPTS_DIR ) )
# ---------- 测试基础设施 ----------
_RESULTS : list [ tuple [ str , bool , str ] ] = [ ]
_VERBOSE = False
def _run ( name : str , fn : Callable [ [ ] , tuple [ bool , str ] ] ) - > None :
""" 运行单个 test_*,记录结果。 """
try :
ok , msg = fn ( )
except Exception as e :
import traceback
tb = traceback . format_exc ( limit = 3 )
ok , msg = False , f " exception: { e } \n { tb } "
_RESULTS . append ( ( name , ok , msg ) )
if _VERBOSE or not ok :
marker = " PASS " if ok else " FAIL "
print ( f " [ { marker } ] { name } : { msg } " , flush = True )
# ---------- util.constants 版本对齐( POLICY-RELEASE-001) ----------
def test_skill_md_version_alignment ( ) - > tuple [ bool , str ] :
""" POLICY-RELEASE-001: SKILL.md version == SKILL_VERSION == CHANGELOG 顶部版本 """
from util . constants import SKILL_VERSION
skill_md = ( _REPO_ROOT / " SKILL.md " ) . read_text ( encoding = " utf-8 " )
m = re . search ( r ' (?m)^version: \ s*[ " \' ]?( \ d+ \ . \ d+ \ . \ d+) ' , skill_md )
if not m :
return False , " SKILL.md 缺顶层 version: X.Y.Z "
md_ver = m . group ( 1 )
if md_ver != SKILL_VERSION :
return False , f " SKILL.md= { md_ver } != SKILL_VERSION= { SKILL_VERSION } "
# CHANGELOG 顶部应有 ## X.Y.Z
cl = ( _REPO_ROOT / " CHANGELOG.md " ) . read_text ( encoding = " utf-8 " )
cl_m = re . search ( r ' (?m)^## \ s+( \ d+ \ . \ d+ \ . \ d+) ' , cl )
if not cl_m :
return False , " CHANGELOG.md 缺顶层 ## X.Y.Z "
cl_ver = cl_m . group ( 1 )
if cl_ver != SKILL_VERSION :
return False , f " CHANGELOG 顶部= { cl_ver } != SKILL_VERSION= { SKILL_VERSION } "
return True , f " SKILL.md= { md_ver } == CHANGELOG= { cl_ver } == SKILL_VERSION= { SKILL_VERSION } "
def test_skill_md_slug_alignment ( ) - > tuple [ bool , str ] :
""" SKILL.md metadata.openclaw.slug == SKILL_SLUG """
from util . constants import SKILL_SLUG
skill_md = ( _REPO_ROOT / " SKILL.md " ) . read_text ( encoding = " utf-8 " )
m = re . search ( r ' (?m)^ \ s*slug: \ s*[ " \' ]?([a-z0-9_-]+) ' , skill_md )
if not m :
return False , " SKILL.md 缺 metadata.openclaw.slug "
md_slug = m . group ( 1 )
if md_slug != SKILL_SLUG :
return False , f " SKILL.md slug= { md_slug } != SKILL_SLUG= { SKILL_SLUG } "
return True , f " slug= { md_slug } "
def test_skill_md_brands_alignment ( ) - > tuple [ bool , str ] :
""" SKILL.md metadata.openclaw.brands == constants.BRANDS( 多平台发布对齐) """
from util . constants import BRANDS
skill_md = ( _REPO_ROOT / " SKILL.md " ) . read_text ( encoding = " utf-8 " )
# 在 SKILL.md 里找 brands: 后的列表内容
m = re . search (
r ' (?ms)^ \ s*brands: \ s*(?:#[^ \ n]* \ n)?(?P<list>(?: \ s*- \ s*[a-z]+ \ s*[^ \ n]* \ n)+) ' ,
skill_md ,
)
if not m :
return False , " SKILL.md 缺 metadata.openclaw.brands 列表 "
listed = re . findall ( r ' - \ s*([a-z0-9_-]+) ' , m . group ( " list " ) )
listed_tuple = tuple ( listed )
if listed_tuple != BRANDS :
return False , f " SKILL.md brands= { listed_tuple } != BRANDS= { BRANDS } "
return True , f " brands= { ' , ' . join ( BRANDS ) } "
# ---------- util.constants 自身一致性 ----------
def test_constants_required_keys_present ( ) - > tuple [ bool , str ] :
""" util.constants 必须含 SKILL_SLUG/SKILL_VERSION/PLATFORM_KIT_MIN_VERSION/BRANDS """
from util import constants
missing = [ ]
for key in ( " SKILL_SLUG " , " SKILL_VERSION " , " PLATFORM_KIT_MIN_VERSION " ,
" BRANDS " , " CANDIDATE_STATUSES " , " REVIEW_CONCLUSIONS " ,
" RISK_DIMENSIONS " , " RISK_LEVEL_LABELS " ) :
if not hasattr ( constants , key ) :
missing . append ( key )
if missing :
return False , f " util.constants 缺字段: { ' , ' . join ( missing ) } "
return True , " 全部必需字段已声明 "
def test_constants_candidate_statuses_complete ( ) - > tuple [ bool , str ] :
""" CANDIDATE_STATUSES 必须覆盖 REQUIREMENTS 0.3 节业务行生命周期的 5 个状态 """
from util . constants import CANDIDATE_STATUSES
required = ( " 待审 " , " 已标注 " , " 已复核 " , " 已驳回 " , " 已撤回 " )
missing = [ s for s in required if s not in CANDIDATE_STATUSES ]
if missing :
return False , f " CANDIDATE_STATUSES 缺: { ' , ' . join ( missing ) } "
if len ( CANDIDATE_STATUSES ) != len ( required ) :
return False , f " CANDIDATE_STATUSES 多了非预期项: { CANDIDATE_STATUSES } "
return True , f " 5 个状态齐全: { ' / ' . join ( CANDIDATE_STATUSES ) } "
def test_constants_review_conclusions_complete ( ) - > tuple [ bool , str ] :
""" REVIEW_CONCLUSIONS 必须含保留 / 取消 / 调减 """
from util . constants import REVIEW_CONCLUSIONS
required = ( " 保留 " , " 取消 " , " 调减 " )
missing = [ s for s in required if s not in REVIEW_CONCLUSIONS ]
if missing :
return False , f " REVIEW_CONCLUSIONS 缺: { ' , ' . join ( missing ) } "
return True , f " { len ( REVIEW_CONCLUSIONS ) } 个结论齐全 "
def test_constants_risk_dimensions_complete ( ) - > tuple [ bool , str ] :
""" RISK_DIMENSIONS 必须含 REQUIREMENTS 列举的 4 个检测维度 """
from util . constants import RISK_DIMENSIONS
required = (
" income_vs_card_consumption " ,
" comprehensive_vs_score_rank " ,
" cumulative_aid_amount " ,
" duplicate_aid_history " ,
)
missing = [ s for s in required if s not in RISK_DIMENSIONS ]
if missing :
return False , f " RISK_DIMENSIONS 缺: { ' , ' . join ( missing ) } "
return True , f " { len ( RISK_DIMENSIONS ) } 个检测维度齐全 "
def test_constants_brands_match_skill_md ( ) - > tuple [ bool , str ] :
""" BRANDS 至少含 jiangchang + huashang( v1.0.2 多平台发布声明) """
from util . constants import BRANDS
for need in ( " jiangchang " , " huashang " ) :
if need not in BRANDS :
return False , f " BRANDS 缺 { need !r} ,当前 { BRANDS } "
return True , f " BRANDS= { list ( BRANDS ) } "
# ---------- main.py CLI 入口 ----------
def test_main_commands_registered ( ) - > tuple [ bool , str ] :
""" scripts/main.py COMMANDS 必须注册全部 REQUIREMENTS 0.1 节列出的能力 """
from main import COMMANDS
required = (
" health " , " version " , " config-path " , " init-db " ,
" verify-candidate " , " batch-verify-class " ,
" mark-needs-review " , " submit-review-result " , " render-html " ,
" sync-card-data " , " sync-gdp-data " ,
)
missing = [ c for c in required if c not in COMMANDS ]
if missing :
return False , f " COMMANDS 缺: { ' , ' . join ( missing ) } "
return True , f " { len ( COMMANDS ) } 个命令已注册 "
def test_main_version_returns_zero ( ) - > tuple [ bool , str ] :
""" version() 命令应返回 0 """
from main import version
buf = io . StringIO ( )
with redirect_stdout ( buf ) :
rc = version ( )
if rc != 0 :
return False , f " version() 返回 { rc } , want 0 "
if not buf . getvalue ( ) . strip ( ) :
return False , " version() 输出为空 "
return True , f " 输出= { buf . getvalue ( ) . strip ( ) !r} "
def test_main_version_matches_skill_version ( ) - > tuple [ bool , str ] :
""" version() 输出应与 SKILL_VERSION 对齐(避免硬编码漂移) """
from main import version
from util . constants import SKILL_VERSION
buf = io . StringIO ( )
with redirect_stdout ( buf ) :
version ( )
out = buf . getvalue ( ) . strip ( )
if SKILL_VERSION not in out :
return False , f " version() 输出 { out !r} 不含 SKILL_VERSION= { SKILL_VERSION } "
return True , f " version() 含 { SKILL_VERSION } "
def test_main_health_returns_zero ( ) - > tuple [ bool , str ] :
""" health() 应返回 0 且输出非空 """
from main import health
buf = io . StringIO ( )
with redirect_stdout ( buf ) :
rc = health ( )
if rc != 0 :
return False , f " health() 返回 { rc } , want 0 "
if not buf . getvalue ( ) . strip ( ) :
return False , " health() 输出为空 "
return True , " health() 返回 0 且有输出 "
def test_main_dispatch_unknown_command_returns_1 ( ) - > tuple [ bool , str ] :
""" main([ ' not-a-real-command ' ]) 应返回 1 并打印 usage """
import main as m
buf = io . StringIO ( )
with redirect_stdout ( buf ) :
rc = m . main ( [ " not-a-real-command " ] )
if rc != 1 :
return False , f " main([ ' not-a-real-command ' ]) = { rc } , want 1 "
if " Usage " not in buf . getvalue ( ) :
return False , f " 应打印 usage, 实际 { buf . getvalue ( ) !r} "
return True , " 未知命令 → 1 + Usage "
def test_main_dispatch_known_command ( ) - > tuple [ bool , str ] :
""" main([ ' health ' ]) 应路由到 health() 并返回 0 """
import main as m
buf = io . StringIO ( )
with redirect_stdout ( buf ) :
rc = m . main ( [ " health " ] )
if rc != 0 :
return False , f " main([ ' health ' ]) = { rc } , want 0 "
if " health " not in buf . getvalue ( ) . lower ( ) :
return False , f " health() 输出应含 ' health ' ,实际 { buf . getvalue ( ) !r} "
return True , " main([ ' health ' ]) 路由正确 "
def test_main_dispatch_no_args_returns_1 ( ) - > tuple [ bool , str ] :
""" main([]) 应返回 1( 无命令) """
import main as m
buf = io . StringIO ( )
with redirect_stdout ( buf ) :
rc = m . main ( [ ] )
if rc != 1 :
return False , f " main([]) = { rc } , want 1 "
return True , " 无命令 → 1 "
# ---------- 入口 ----------
def main ( ) - > int :
parser = argparse . ArgumentParser ( description = " 默认 release 测试套件 " )
parser . add_argument ( " -v " , " --verbose " , action = " store_true " ,
help = " 详细输出每个用例结果 " )
args = parser . parse_args ( )
global _VERBOSE
_VERBOSE = args . verbose
print ( " === verify-scholarship-fraud-sas release tests === " , flush = True )
_run ( " constants/skill_md_version_alignment " , test_skill_md_version_alignment )
_run ( " constants/skill_md_slug_alignment " , test_skill_md_slug_alignment )
_run ( " constants/skill_md_brands_alignment " , test_skill_md_brands_alignment )
_run ( " constants/required_keys_present " , test_constants_required_keys_present )
_run ( " constants/candidate_statuses_complete " , test_constants_candidate_statuses_complete )
_run ( " constants/review_conclusions_complete " , test_constants_review_conclusions_complete )
_run ( " constants/risk_dimensions_complete " , test_constants_risk_dimensions_complete )
_run ( " constants/brands_match_skill_md " , test_constants_brands_match_skill_md )
_run ( " main/commands_registered " , test_main_commands_registered )
_run ( " main/version_returns_zero " , test_main_version_returns_zero )
_run ( " main/version_matches_skill_version " , test_main_version_matches_skill_version )
_run ( " main/health_returns_zero " , test_main_health_returns_zero )
_run ( " main/dispatch_unknown_command " , test_main_dispatch_unknown_command_returns_1 )
_run ( " main/dispatch_known_command " , test_main_dispatch_known_command )
_run ( " main/dispatch_no_args " , test_main_dispatch_no_args_returns_1 )
total = len ( _RESULTS )
passed = sum ( 1 for _ , ok , _ in _RESULTS if ok )
failed = total - passed
print ( )
print ( f " === Summary: { passed } / { total } passed, { failed } failed === " , flush = True )
if failed :
print ( " \n Failures: " )
for name , ok , msg in _RESULTS :
if not ok :
print ( f " - { name } : { msg } " )
return 1
return 0
if __name__ == " __main__ " :
raise SystemExit ( main ( ) )