Files
gplan-2/game/tests/unit/schema_vocab_violation_test.gd
T

170 lines
9.5 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tests/unit/schema_vocab_violation_test.gd — schema 词表违约词的拒绝路径集中断言面(迭代 55「QA覆盖余量收口批」T1/V1)
# 覆盖:十七违约词族七检查——①empty_table 八表空行集注入(board_gen/enemy/level/meta/reward/shop/skill/status_effect)
# ②settlement missing_segment/pperfect_oob/segorder_violation 三注入 ③feat+treasure zero_flat 白名单违约注入
# ④meta attune_refresh_upgrade keystone_cost 越域注入 ⑤treasure use_phase 出域注入(USE_PHASES=active_dig 枚值面)
# ⑥board_gen solver_rules="single" frozen 拒(同钉 single_subset 冻结语义) ⑦meta_tree 彩蛋 4 行(>3)load_bearing 拒
# 注入形态沿在役先例(meta_progression_forge_test.gd:581 同款):load(schema).new() 新实例+真行深拷贝注入——
# 禁改共享 Resource(共享 Resource 池污染 Gotcha)。违约词经 validate() 诊断面直调取 errs 断言(非致命通道,
# 不进 manifest negative 名单——本面非运行期致命路径,不触 test-conventions §5.6)。
# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload,ConfigDB 显式实例化构造注入。
# 运行:godot --headless --path game --script res://tests/unit/schema_vocab_violation_test.gd(expect 0)
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
const ConfigDBScript := preload("res://core/config_db.gd")
var _db
func _init() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (违约词拒绝路径——validate 诊断面直调断言宿主)
_db = ConfigDBScript.new()
# 表加载失败不中止(collect_only=true——诊断在此展开,红由具体断言落,沿 guard 套件同款)
var load_ok: bool = _db.load_tables("res://data/tables", true)
if not load_ok:
push_error("[schema_vocab_violation_test] 表加载返回失败——诊断如下(红由具体断言落)")
for d in _db.diagnostics():
if d.severity == "error" or d.severity == "fatal":
push_error(" [%s] %s: %s" % [d.severity, d.category, d.message])
_check_empty_tables()
_check_settlement_vocab()
_check_zero_flat_guard()
_check_keystone_cost_domain()
_check_use_phase_domain()
_check_solver_frozen()
_check_meta_tree_load_bearing()
quit(Kit.finish("schema_vocab_violation_test"))
# errs 数组按 category(可选 message 子串)命中判定
func _has(errs: Array, cat: String, needle: String = "") -> bool:
for e in errs:
if String(e["category"]) != cat:
continue
if needle == "" or String(e["message"]).contains(needle):
return true
return false
# ---- ① empty_table 八表空行集注入(fresh 新实例 rows/行族全空)----
func _check_empty_tables() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (空表违约词拒绝路径——validate 诊断面直调取 errs)
var schemas := {
"board_gen": "res://data/schemas/board_gen_table.gd",
"enemy": "res://data/schemas/enemy_table.gd",
"level": "res://data/schemas/level_table.gd",
"meta": "res://data/schemas/meta_table.gd",
"reward": "res://data/schemas/reward_table.gd",
"shop": "res://data/schemas/shop_table.gd",
"skill": "res://data/schemas/skill_table.gd",
"status_effect": "res://data/schemas/status_effect_table.gd",
}
for table_name in schemas.keys():
var t = load(String(schemas[table_name])).new()
Kit.check("G1-① 空表注入被拒:%s validate() errs 含 empty_table〔QA余 V1〕" % table_name,
_has(t.validate(), "empty_table"))
# ---- ② settlement 三注入(missing_segment/pperfect_oob/segorder_violation)----
func _check_settlement_vocab() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (settlement 三违约词拒绝路径——validate 诊断面直调取 errs)
# missing_segment:fresh 新实例 core/damage_roll 段缺失
var seg_missing = load("res://data/schemas/settlement_table.gd").new()
Kit.check("G1-② settlement 段缺失注入被拒:errs 含 missing_segment〔QA余 V1〕",
_has(seg_missing.validate(), "missing_segment"))
# pperfect_oob:真表深拷贝改 p_perfect=150(载体域 {100,200} 外——禁改共享 Resource,deep duplicate 隔离)
var st = (_db.combat.settlement as Resource).duplicate(true)
st.core.p_perfect = 150
Kit.check("G1-② p_perfect=150(域 {100,200} 外)注入被拒:errs 含 pperfect_oob〔QA余 V1〕",
_has(st.validate(), "pperfect_oob"))
# segorder_violation:真表深拷贝改段序(FROZEN_SEGMENT_ORDER 恒等面外)
var st2 = (_db.combat.settlement as Resource).duplicate(true)
st2.segment_order = PackedStringArray(["verdict", "buff_debuff", "combat", "decay", "grant"])
Kit.check("G1-② segment_order 违例注入被拒:errs 含 segorder_violation〔QA余 V1〕",
_has(st2.validate(), "segorder_violation"))
# ---- ③ zero_flat 白名单违约注入(feat+treasure 两表)----
func _check_zero_flat_guard() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (零裸 flat 白名单违约词拒绝路径——validate 诊断面直调取 errs)
# feat:真行深拷贝+白名单外键(ROW_KEYS 外)
var feat_row: Dictionary = (_db.crossrun.feats.rows[0] as Dictionary).duplicate(true)
feat_row["ghost_flat"] = 1
var ft = load("res://data/schemas/feat_table.gd").new()
ft.rows = [feat_row]
Kit.check("G1-③ feat 行键出白名单注入被拒:errs 含 zero_flat〔QA余 V1〕", _has(ft.validate(), "zero_flat"))
# treasure:真行深拷贝+白名单外键(KEY_WHITELIST 外)
var tr_row: Dictionary = (_db.crossrun.treasure_items.rows[0] as Dictionary).duplicate(true)
tr_row["ghost_flat"] = 1
var tt = load("res://data/schemas/treasure_item_table.gd").new()
tt.rows = [tr_row]
Kit.check("G1-③ treasure 行键出白名单注入被拒:errs 含 zero_flat〔QA余 V1〕", _has(tt.validate(), "zero_flat"))
# ---- ④ meta attune_refresh_upgrade keystone_cost 越域注入 ----
func _check_keystone_cost_domain() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (keystone_cost 越域拒绝路径——validate 诊断面直调取 errs)
var bad: Dictionary = (_db.crossrun.meta.attune_refresh_upgrade[0] as Dictionary).duplicate(true)
bad["keystone_cost"] = 1000 # 域 [1,999] 外
var mt = load("res://data/schemas/meta_table.gd").new()
var bad_family: Array[Dictionary] = [bad] # Array[Dictionary] 严格型——未类型化字面量赋值会 SCRIPT ERROR 中断
mt.attune_refresh_upgrade = bad_family
Kit.check("G1-④ meta attune_refresh_upgrade keystone_cost=1000 越域注入被拒:errs 含 domain(keystone_cost 越域)〔QA余 V1〕",
_has(mt.validate(), "domain", "keystone_cost 越域"))
# ---- ⑤ treasure use_phase 出域注入(覆盖 active_dig 枚值面)----
func _check_use_phase_domain() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (use_phase 出域拒绝路径——validate 诊断面直调取 errs)
var TT = load("res://data/schemas/treasure_item_table.gd")
# 枚值面钉:USE_PHASES 单元素 = active_dig(出域注入覆盖的正是该枚值面)
Kit.check("G1-⑤ use_phase 枚值面钉:USE_PHASES=[active_dig]〔QA余 V1〕", TT.USE_PHASES == ["active_dig"])
var bad: Dictionary = (_db.crossrun.treasure_items.rows[0] as Dictionary).duplicate(true)
bad["use_phase"] = "post_clear_x" # 出域
var tt = TT.new()
tt.rows = [bad]
Kit.check("G1-⑤ treasure use_phase 出域注入被拒:errs 含 enum(use_phase 出域)〔QA余 V1〕",
_has(tt.validate(), "enum", "use_phase 出域"))
# ---- ⑥ board_gen solver_rules="single" frozen 拒(同钉 single_subset 冻结语义)----
func _check_solver_frozen() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (solver_rules 冻结语义拒绝路径——validate 诊断面直调取 errs)
# 冻结语义正证:真行(single_subset)零 frozen 违例——frozen 只拒 non-single_subset
var good: Dictionary = (_db.combat.board_gen.rows[0] as Dictionary).duplicate(true)
var bt = load("res://data/schemas/board_gen_table.gd").new()
var good_rows: Array[Dictionary] = [good] # Array[Dictionary] 严格型——未类型化字面量赋值会 SCRIPT ERROR 中断
bt.rows = good_rows
Kit.check("G1-⑥ 冻结语义正证:solver_rules=single_subset 真行零 frozen 违例〔QA余 V1〕",
not _has(bt.validate(), "frozen"))
# 冻结拒:single 在 SOLVER_RULES 词表内(enum 合法)但被冻结面拒——注入仅命中 frozen 违约词
var bad: Dictionary = (_db.combat.board_gen.rows[0] as Dictionary).duplicate(true)
bad["solver_rules"] = "single"
var bt2 = load("res://data/schemas/board_gen_table.gd").new()
var bad_rows: Array[Dictionary] = [bad]
bt2.rows = bad_rows
Kit.check("G1-⑥ solver_rules=single(enum 合法、冻结面外)注入被拒:errs 含 frozen〔QA余 V1〕",
_has(bt2.validate(), "frozen"))
# ---- ⑦ meta_tree 彩蛋 4 行(>3)load_bearing 注入 ----
func _check_meta_tree_load_bearing() -> void:
# 判据出处: design/engineering/test-conventions.md:67 (彩蛋上限违约词拒绝路径——validate 诊断面直调取 errs)
var easter: Dictionary = (_db.crossrun.meta_tree.row_by_id("easter_ley") as Dictionary).duplicate(true)
var mt = load("res://data/schemas/meta_tree_table.gd").new()
var bad_rows: Array = []
for i in range(4):
var r: Dictionary = easter.duplicate(true)
r["node_id"] = "easter_x%d" % i
bad_rows.append(r)
mt.rows = bad_rows
Kit.check("G1-⑦ meta_tree 彩蛋 4 行(>3)注入被拒:errs 含 load_bearing(彩蛋节点 4 > 3)〔QA余 V1〕",
_has(mt.validate(), "load_bearing", "彩蛋节点"))