Files
gplan-2/game/tests/unit/contracts/system_contracts_test.gd

125 lines
6.5 KiB
GDScript
Raw Permalink 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/contracts/system_contracts_test.gd — 系统间接口契约断言(宁缺勿错——只断言已定稿行为)
# 运行:godot --headless --path game --script res://tests/unit/contracts/system_contracts_test.gd
# 契约线清单来源:design/engineering/architecture/architecture.md「API Boundaries」表 + 各系统 AC 前言(规范源)。
# 覆盖面:
# 各系统→D2 ConfigDB 类型化单点(TABLE_MANIFEST 词表封闭——伤害单据 kind/目标规则/结账政策常量);
# D2↔B1 掷点用途标签 ∈ RngService 封闭词表(EC12——settlement.armor_roll_stream 成员性);
# B1→D1 行协议公共字段形态(open_ticket 九栏缺省 + 协议公共键);
# B4→B3 增益授予布尔受理口径(域闭 EC1 / 未知 id EC2 / 受理后投影)。
extends SceneTree
const ConfigDBScript := preload("res://core/config_db.gd")
const RngServiceScript := preload("res://core/rng_service.gd")
const StatusContainerScript := preload("res://core/status_container.gd")
const UnitContainerScript := preload("res://core/unit_container.gd")
const DamageControllerScript := preload("res://core/damage_controller.gd")
const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇总退出样板单点
func _init() -> void:
var db = ConfigDBScript.new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and db.diagnostics_count("error") == 0)
if load_ok:
_test_damage_controller_vocabularies()
_test_rng_tag_closure(db)
_test_ticket_nine_columns()
_test_status_boolean_acceptance(db)
quit(Kit.finish("system_contracts_test"))
# ---- B1 兑付配方词表:五配方/三目标规则/双结账政策——常量非空且互异(封闭枚举负空间) ----
func _test_damage_controller_vocabularies() -> void:
var kinds: Array = [
DamageControllerScript.KIND_HERO_ATTACK, DamageControllerScript.KIND_ENEMY_SKILL,
DamageControllerScript.KIND_BOARD_TYPED, DamageControllerScript.KIND_BOARD_BLAST,
DamageControllerScript.KIND_BOARD_FLAT_HERO,
]
var nonempty := true
for k in kinds:
if String(k).is_empty():
nonempty = false
Kit.check("五配方常量非空(开单面唯一分派键)", nonempty)
var unique_kinds: Dictionary = {}
for k in kinds:
unique_kinds[String(k)] = true
Kit.check("五配方常量互异(词表封闭无别名)", unique_kinds.size() == kinds.size())
var targets: Array = [
DamageControllerScript.TARGET_FRONT_ALIVE, DamageControllerScript.TARGET_HERO,
DamageControllerScript.TARGET_ALL_ALIVE,
]
var unique_targets: Dictionary = {}
for t in targets:
unique_targets[String(t)] = true
Kit.check("三目标规则常量互异(结账时刻现场解析词表)", unique_targets.size() == targets.size())
Kit.check("双结账政策常量互异(A 即结 / B 攒批回切位)",
String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT))
# ---- D2↔B1:掷点用途标签 ∈ RngService 封闭词表(EC12——跨系统字面量契约的成员性断言) ----
func _test_rng_tag_closure(db) -> void:
var stream_label: String = String(db.combat.settlement.damage_roll.armor_roll_stream)
Kit.check("settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)",
RngServiceScript.PURPOSE_TAGS.has(stream_label))
var unique_tags: Dictionary = {}
for t in RngServiceScript.PURPOSE_TAGS:
unique_tags[String(t)] = true
Kit.check("RngService 用途标签词表非空且互异(封闭语义由流改标签)",
RngServiceScript.PURPOSE_TAGS.size() > 0 and unique_tags.size() == RngServiceScript.PURPOSE_TAGS.size())
# ---- B1→D1 行协议:单据九栏缺省齐备 + 协议公共键形态 ----
func _test_ticket_nine_columns() -> void:
var ctrl = _bare_controller()
var t: Dictionary = ctrl.open_ticket(DamageControllerScript.KIND_BOARD_FLAT_HERO, "mine_hit", "契约探针",
{"channel": "棋盘兑付", "base_value": 1, "target_rule": DamageControllerScript.TARGET_HERO, "ri": 0})
var columns := ["kind", "source_class", "origin_cell", "round_index", "display",
"channel", "attack_type", "base_value", "target_rule", "rider", "highlight", "context",
"requires", "source_unit"]
var all_present := true
for c in columns:
if not t.has(c):
all_present = false
Kit.check("单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)", all_present)
Kit.check("缺省无类型轴(attack_type 空 = 恒不查克制矩阵)", String(t.attack_type) == "")
Kit.check("缺省无前置依赖(requires = -1)", int(t.requires) == -1)
func _bare_controller():
var db = ConfigDBScript.new()
# 只取 open_ticket 纯构造面——四个依赖一律传真实实例,不用空桩:
# DamageController 的 db/rng/units/effects 全是定型标注,传无类型的空桩会触发
# 「Trying to assign value of type '' to a variable of type '…'」脚本错误——该错误不改退出码,
# 却让赋值静默失败(成员留 null),脚本错误扫描落地后必红。三者均可零消费构造。
var units = UnitContainerScript.new(db)
var effects = StatusContainerScript.new(db)
return DamageControllerScript.new(db, RngServiceScript.new(), units, effects)
# ---- B4→B3:增益授予布尔受理口径(受理/拒绝二元面 + 投影一致性) ----
func _test_status_boolean_acceptance(db) -> void:
var effects = StatusContainerScript.new(db)
# EC1 域闭:notify_battle_start 前授予 = false(受理面封闭)
var any_row: Dictionary = {}
if db.combat.status_effects.rows.size() > 0:
any_row = db.combat.status_effects.rows[0]
Kit.check("前置:状态表非空", not any_row.is_empty())
if any_row.is_empty():
return
var probe_id: String = String(any_row.effect_id)
var src := {"source_class": "safe_cell", "origin_cell": Vector2i(-1, -1), "round_index": 0}
Kit.check("域闭授予拒绝(EC1——战斗域外 apply_effect = false)", effects.apply_effect(probe_id, src) == false)
Kit.check("未知 effect_id 拒绝(EC2)", effects.apply_effect("no_such_effect_id__", src) == false)
effects.notify_battle_start()
Kit.check("开域后受理已知 id(布尔受理口径 true 通道)", effects.apply_effect(probe_id, src) == true)
var active: Array = effects.query_active(String(any_row.target_side))
var projected := false
for a in active:
if String(a.effect_id) == probe_id:
projected = int(a.effective_value) == int(a.layers) * int(a.snap_magnitude)
Kit.check("受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)", projected)