Files
mtgodot-poc/project/test_security_lock_parity.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight
  - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程
  - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题
  - 新增 test_bridge_height_parity.gd 自动化对拍测试
- 40250 怪物击杀经验动效:
  - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附
- 40250 客户端全系统功能对齐(Batches 1-31):
  - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试
- 文档沉淀:
  - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

220 lines
9.0 KiB
GDScript

# test_security_lock_parity.gd —— 40250 仓库安全密码与装备防丢锁定 1:1 纯净回归测试套件
extends SceneTree
const SecurityLockSystem = preload("res://security_lock_system.gd")
var _passed := 0
var _failed := 0
func _assert(cond: bool, msg: String) -> void:
if cond:
_passed += 1
print(" [PASS] %s" % msg)
else:
_failed += 1
printerr(" [FAIL] %s" % msg)
func _init() -> void:
print("\n=== Running test_security_lock_parity (Direction 4: Security Lock System 1:1) ===")
_test_safebox_password_auth()
_test_safebox_password_change()
_test_item_security_lock_and_unlock()
_test_item_action_gates()
_test_character_deletion_code()
_finish()
func _test_safebox_password_auth() -> void:
print("\n--- Test 1: Safebox Password Authentication & Lockout ---")
var sec := SecurityLockSystem.new()
var unlock_data := {"fired": false}
sec.safebox_unlocked.connect(func(): unlock_data["fired"] = true)
_assert(sec.safebox_password == "000000", "Default safebox password is '000000'")
_assert(sec.is_safebox_unlocked == false, "Safebox initially closed")
# Wrong password 1st time
var res1 = sec.open_safebox("111111")
_assert(res1["ok"] == false and res1["reason"] == "WRONG_PASSWORD", "Wrong password fails")
_assert(res1["remaining_attempts"] == 4, "4 attempts remaining")
# Fail 4 more times to reach 5
sec.open_safebox("111111")
sec.open_safebox("111111")
sec.open_safebox("111111")
var res5 = sec.open_safebox("111111")
_assert(res5["ok"] == false and res5["reason"] == "WRONG_PASSWORD_LOCKOUT", "5th failure triggers lockout")
_assert(sec.is_locked_out == true, "is_locked_out is true")
# Blocked while locked out even with correct password
var locked_res = sec.open_safebox("000000")
_assert(locked_res["ok"] == false and locked_res["reason"] == "LOCKOUT", "Action blocked while locked out")
# Reset lockout
sec.reset_lockout()
_assert(sec.is_locked_out == false and sec.failed_attempts == 0, "Lockout reset successfully")
# Now correct password opens safebox
var ok_res = sec.open_safebox("000000")
_assert(ok_res["ok"] == true, "Correct password opened safebox")
_assert(sec.is_safebox_unlocked == true, "Safebox is unlocked")
_assert(unlock_data["fired"] == true, "safebox_unlocked signal fired")
# Close safebox
var close_data := {"fired": false}
sec.safebox_closed.connect(func(): close_data["fired"] = true)
sec.close_safebox()
_assert(sec.is_safebox_unlocked == false, "Safebox closed")
_assert(close_data["fired"] == true, "safebox_closed signal fired")
func _test_safebox_password_change() -> void:
print("\n--- Test 2: Password Change Validation (Strict 6 Digits) ---")
var sec := SecurityLockSystem.new()
# Wrong old password
var r1 = sec.change_safebox_password("999999", "123456")
_assert(r1["ok"] == false and r1["reason"] == "WRONG_OLD_PASSWORD", "Wrong old password rejected")
# Invalid format: 5 digits
var r2 = sec.change_safebox_password("000000", "12345")
_assert(r2["ok"] == false and r2["reason"] == "INVALID_PASSWORD_FORMAT", "5-digit password rejected")
# Invalid format: 7 digits
var r3 = sec.change_safebox_password("000000", "1234567")
_assert(r3["ok"] == false and r3["reason"] == "INVALID_PASSWORD_FORMAT", "7-digit password rejected")
# Invalid format: Contains non-digits
var r4 = sec.change_safebox_password("000000", "12a456")
_assert(r4["ok"] == false and r4["reason"] == "INVALID_PASSWORD_FORMAT", "Alphanumeric password rejected")
# Same password
var r5 = sec.change_safebox_password("000000", "000000")
_assert(r5["ok"] == false and r5["reason"] == "SAME_PASSWORD", "Identical new password rejected")
# Valid change to "888888"
var pwd_data := {"fired": false, "new_pwd": ""}
sec.password_changed.connect(func(np: String):
pwd_data["fired"] = true
pwd_data["new_pwd"] = np
)
var r6 = sec.change_safebox_password("000000", "888888")
_assert(r6["ok"] == true, "Valid 6-digit password changed successfully")
_assert(sec.safebox_password == "888888", "Active password updated to '888888'")
_assert(pwd_data["fired"] == true and pwd_data["new_pwd"] == "888888", "password_changed signal fired")
# Verify old password no longer works
var old_try = sec.open_safebox("000000")
_assert(old_try["ok"] == false, "Old password '000000' no longer valid")
# Verify new password works
var new_try = sec.open_safebox("888888")
_assert(new_try["ok"] == true, "New password '888888' valid")
func _test_item_security_lock_and_unlock() -> void:
print("\n--- Test 3: Item Anti-Theft Lock & Unlock ---")
var sec := SecurityLockSystem.new()
var test_item: Dictionary = {
"vnum": 169,
"name": "战神之剑+9",
"count": 1
}
var lock_signal := {"fired": false, "name": ""}
sec.item_locked.connect(func(n: String):
lock_signal["fired"] = true
lock_signal["name"] = n
)
var unlock_signal := {"fired": false, "name": ""}
sec.item_unlocked.connect(func(n: String):
unlock_signal["fired"] = true
unlock_signal["name"] = n
)
# Lock with wrong password
var r1 = sec.lock_item(test_item, "999999")
_assert(r1["ok"] == false and r1["reason"] == "WRONG_SECURITY_PASSWORD", "Wrong password prevents locking")
# Lock with correct password
var r2 = sec.lock_item(test_item, "000000")
_assert(r2["ok"] == true, "Item locked successfully")
_assert(test_item["is_locked"] == true, "Item is_locked flag is true")
_assert(lock_signal["fired"] == true and lock_signal["name"] == "战神之剑+9", "item_locked signal fired")
# Attempt to lock already locked item
var r3 = sec.lock_item(test_item, "000000")
_assert(r3["ok"] == false and r3["reason"] == "ALREADY_LOCKED", "Already locked item rejected")
# Unlock with wrong password
var r4 = sec.unlock_item(test_item, "999999")
_assert(r4["ok"] == false and r4["reason"] == "WRONG_SECURITY_PASSWORD", "Wrong password prevents unlocking")
# Unlock with correct password
var r5 = sec.unlock_item(test_item, "000000")
_assert(r5["ok"] == true, "Item unlocked successfully")
_assert(test_item["is_locked"] == false, "Item is_locked flag is false")
_assert(unlock_signal["fired"] == true and unlock_signal["name"] == "战神之剑+9", "item_unlocked signal fired")
# Attempt to unlock unlocked item
var r6 = sec.unlock_item(test_item, "000000")
_assert(r6["ok"] == false and r6["reason"] == "NOT_LOCKED", "Already unlocked item rejected")
func _test_item_action_gates() -> void:
print("\n--- Test 4: Action Protection Gates (Drop, Sell, Destroy, Trade, Refine) ---")
var sec := SecurityLockSystem.new()
var normal_item: Dictionary = {"vnum": 10, "name": "木剑+0", "is_locked": false}
var locked_item: Dictionary = {"vnum": 11299, "name": "黑钢甲+9", "is_locked": true}
# Normal item allowed everywhere
_assert(sec.can_drop_item(normal_item)["allowed"] == true, "Normal item can be dropped")
_assert(sec.can_sell_item(normal_item)["allowed"] == true, "Normal item can be sold")
_assert(sec.can_destroy_item(normal_item)["allowed"] == true, "Normal item can be destroyed")
_assert(sec.can_trade_item(normal_item)["allowed"] == true, "Normal item can be traded")
_assert(sec.can_refine_item(normal_item)["allowed"] == true, "Normal item can be refined")
# Locked item blocked everywhere with ITEM_LOCKED
var drop_gate = sec.can_drop_item(locked_item)
_assert(drop_gate["allowed"] == false and drop_gate["reason"] == "ITEM_LOCKED", "Locked item DROP blocked")
var sell_gate = sec.can_sell_item(locked_item)
_assert(sell_gate["allowed"] == false and sell_gate["reason"] == "ITEM_LOCKED", "Locked item SELL blocked")
var destroy_gate = sec.can_destroy_item(locked_item)
_assert(destroy_gate["allowed"] == false and destroy_gate["reason"] == "ITEM_LOCKED", "Locked item DESTROY blocked")
var trade_gate = sec.can_trade_item(locked_item)
_assert(trade_gate["allowed"] == false and trade_gate["reason"] == "ITEM_LOCKED", "Locked item TRADE blocked")
var refine_gate = sec.can_refine_item(locked_item)
_assert(refine_gate["allowed"] == false and refine_gate["reason"] == "ITEM_LOCKED", "Locked item REFINE blocked")
func _test_character_deletion_code() -> void:
print("\n--- Test 5: 7-Digit Character Deletion Security Code ---")
var sec := SecurityLockSystem.new()
_assert(sec.char_delete_code == "1234567", "Default 7-digit deletion code is '1234567'")
# Invalid length: 6 digits
var r1 = sec.verify_character_deletion("123456")
_assert(r1["ok"] == false and r1["reason"] == "INVALID_CODE_FORMAT", "6-digit deletion code rejected")
# Invalid format: non-numeric
var r2 = sec.verify_character_deletion("123456a")
_assert(r2["ok"] == false and r2["reason"] == "INVALID_CODE_FORMAT", "Alphanumeric deletion code rejected")
# Wrong 7-digit code
var r3 = sec.verify_character_deletion("7654321")
_assert(r3["ok"] == false and r3["reason"] == "WRONG_DELETE_CODE", "Wrong deletion code rejected")
# Correct 7-digit code
var r4 = sec.verify_character_deletion("1234567")
_assert(r4["ok"] == true, "Correct 7-digit code authorizes deletion")
func _finish() -> void:
print("\n==========================================")
print("Security Lock Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
print("==========================================")
if _failed > 0:
quit(1)
else:
quit(0)