Files
Game-JAM/tests/test_main_attacks.gd

275 lines
9.5 KiB
GDScript

extends SceneTree
## Headless-тест атак часа на экране Main.tscn.
##
## Проверяет UI-интеграцию AttackDirector:
## • junk: карточка в приёме → drop на ShieldDrop → +5 $ / +1 SLA;
## • DDoS: плашка в ПРИЁМЕ и удержание щита 1.5 с → +30 $ / +2 SLA;
## • phishing: приглушённый RequestCard → отклонение → +20 $ / +2 SLA;
## • ransomware: карточка .exe → щит-карантин → +25 $ / +2 SLA;
## • обычный пакет на щите: −2 SLA, пакет остаётся.
##
## Запуск:
## godot --headless --path . --script tests/test_main_attacks.gd
const MAIN_SCENE := "res://scenes/Main.tscn"
var _checks := 0
var _fails := 0
var _main: Node = null
var _controller = null
var _director = null
var _game = null
var _folder_id := ""
func _init() -> void:
await process_frame
var packed := load(MAIN_SCENE) as PackedScene
if packed == null:
_fail("не удалось загрузить Main.tscn")
_finish()
return
_main = packed.instantiate()
root.add_child(_main)
await process_frame
_main._start_new_run()
await process_frame
_game = root.get_node_or_null("Game")
_ok(_game != null, "autoload Game доступен")
_ok(_main.controller != null, "controller создан")
if _game == null or _main.controller == null:
_finish()
return
_controller = _main.controller
_director = _controller.hour_director
_ok(_director != null, "hour_director доступен")
if _director == null:
_finish()
return
# Атаки открываются прогрессией: junk/ddos — час 2, phishing — 3,
# ransomware — 4. Ставим час 4, чтобы проверить все виды.
_game.hour = 4
_ok(_game.feature_unlocked("attack_junk"), "junk-атаки открыты (час 2)")
_ok(_game.feature_unlocked("attack_ddos"), "DDoS-атаки открыты (час 2)")
_ok(_game.feature_unlocked("attack_phishing"), "phishing-атаки открыты (час 3)")
_ok(_game.feature_unlocked("attack_ransomware"), "ransomware-атаки открыты (час 4)")
_ok(_main._shield_receive != null and _main._shield_send != null, "щиты ПРИЁМ/ОТПРАВКА созданы")
_folder_id = _first_folder_id()
_ok(not _folder_id.is_empty(), "в реестре есть папка")
for i in 4:
_ok(_game.registry.create_file("атака_%d" % i, "text", _folder_id) != null,
"создан файл атака_%d" % i)
_main._on_start_hour()
await process_frame
_eq(_controller.phase, &"hour", "фаза контроллера — hour")
_ok(_director.attack_director != null, "attack_director доступен")
await _test_junk()
await _test_ddos()
await _test_phishing()
await _test_ransomware()
await _test_regular_packet()
await process_frame
print("")
print("Проверок: %d, провалов: %d" % [_checks, _fails])
_finish()
# --- сценарии ---------------------------------------------------------------
func _test_junk() -> void:
var aid: String = _director.spawn_attack_now("junk")
_ok(not aid.is_empty(), "junk-атака создана")
if aid.is_empty():
return
_main._refresh_hour()
await process_frame
var card = _find_packet_attack("junk")
_ok(card != null, "junk-карточка в приёме")
if card == null:
return
var pid: String = str(card.id)
var payload := {
"kind": "packet", "id": pid, "name": "?",
"is_attack": true, "attack_kind": "junk", "attack_id": aid,
}
var shield = _main._shield_receive
_ok(shield._can_drop_data(Vector2.ZERO, payload), "щит принимает junk-карточку")
_ok(not shield._can_drop_data(Vector2.ZERO, {"kind": "packet", "id": pid}),
"обычный пакет щит не принимает")
_game.sla = 150.0
var money0 := int(_game.money)
shield._drop_data(Vector2.ZERO, payload)
await process_frame
_eq(int(_game.money), money0 + 5, "junk: +5 $")
_ok(absf(float(_game.sla) - 151.0) < 0.01, "junk: +1 SLA (%.1f)" % float(_game.sla))
_ok(not _director.active_packets.has(pid), "junk-пакет удалён")
_ok(not _director.attack_director.active_attacks.has(aid), "junk-атака снята")
_ok(_find_packet_attack("junk") == null, "junk-карточка убрана из приёма")
func _test_ddos() -> void:
var aid: String = _director.spawn_attack_now("ddos")
_ok(not aid.is_empty(), "DDoS-атака создана")
if aid.is_empty():
return
_main._refresh_hour()
await process_frame
_ok(_main._ddos_overlay != null, "DDoS-плашка показана в ПРИЁМЕ")
_ok(float(_main._packet_shift) > 0.0, "карточки приёма сдвинуты под плашкой")
_game.sla = 150.0
var money0 := int(_game.money)
var held = _director.hold_shield(1.6)
_ok(held, "щит удерживается при активном DDoS")
_eq(int(_game.money), money0 + 30, "DDoS: +30 $")
_ok(absf(float(_game.sla) - 152.0) < 0.01, "DDoS: +2 SLA (%.1f)" % float(_game.sla))
_ok(not _director.attack_director.active_attacks.has(aid), "DDoS-атака отражена")
_main._refresh_hour()
await process_frame
_ok(_main._ddos_overlay == null, "DDoS-плашка убрана после отражения")
func _test_phishing() -> void:
var aid: String = _director.spawn_attack_now("phishing")
_ok(not aid.is_empty(), "phishing-атака создана")
if aid.is_empty():
return
_main._refresh_hour()
await process_frame
var attack: Dictionary = _director.attack_director.active_attacks.get(aid, {})
var rid := str(attack.get("request_id", ""))
_ok(not rid.is_empty(), "у фишинга есть request_id")
var card = _find_request(rid)
_ok(card != null, "RequestCard фишинга в ОТПРАВКЕ")
if card == null:
return
_ok(card.avatar_muted, "аватар фишинга приглушён")
_ok(card.attack_kind == "phishing", "карточка помечена как phishing")
_game.sla = 150.0
var money0 := int(_game.money)
_main._on_request_reject(rid)
await process_frame
_eq(int(_game.money), money0 + 20, "phishing: +20 $")
_ok(absf(float(_game.sla) - 152.0) < 0.01, "phishing: +2 SLA (%.1f)" % float(_game.sla))
_ok(not _director.active_requests.has(rid), "фишинг-запрос закрыт")
_ok(not _director.attack_director.active_attacks.has(aid), "фишинг-атака снята")
func _test_ransomware() -> void:
var aid: String = _director.spawn_attack_now("ransomware")
_ok(not aid.is_empty(), "ransomware-атака создана")
if aid.is_empty():
return
_main._refresh_hour()
await process_frame
var card = _find_packet_attack("ransomware")
_ok(card != null, "ransomware-карточка в приёме")
if card == null:
return
_ok(str(card.file_name).to_lower().ends_with(".exe"), "шифровальщик показан как .exe")
var pid: String = str(card.id)
_game.sla = 150.0
var money0 := int(_game.money)
var ok: bool = _main._on_quarantine_drop({
"kind": "packet", "id": pid,
"is_attack": true, "attack_kind": "ransomware", "attack_id": aid,
})
_ok(ok, "ransomware обезврежен через щит")
await process_frame
_eq(int(_game.money), money0 + 25, "ransomware: +25 $")
_ok(absf(float(_game.sla) - 152.0) < 0.01, "ransomware: +2 SLA (%.1f)" % float(_game.sla))
_ok(not _director.active_packets.has(pid), "ransomware-пакет удалён")
_ok(not _director.attack_director.active_attacks.has(aid), "ransomware-атака снята")
func _test_regular_packet() -> void:
var pid: String = _director.spawn_packet_now("text", "udp")
_ok(not pid.is_empty(), "обычный UDP-пакет заспавнен")
if pid.is_empty():
return
# UDP-потеря (20 %) — для теста возвращаем карточку сразу.
if str(_director.active_packets[pid].state) == "lost":
_director.active_packets[pid].state = "waiting"
_main._refresh_hour()
await process_frame
_game.sla = 150.0
var ok: bool = _main._on_quarantine_drop({
"kind": "packet", "id": pid, "name": "обычный",
"protocol": "UDP", "type": "text",
})
_ok(not ok, "обычный пакет не считается угрозой")
_ok(absf(float(_game.sla) - 148.0) < 0.01, "обычный пакет: −2 SLA (%.1f)" % float(_game.sla))
_ok(_director.active_packets.has(pid), "обычный пакет остался в приёме")
# --- хелперы ----------------------------------------------------------------
func _find_packet_attack(kind: String):
for c in _main.packets:
if is_instance_valid(c) and c.is_attack and str(c.attack_kind) == kind:
return c
return null
func _find_request(req_id: String):
for c in _main.requests:
if is_instance_valid(c) and str(c.id) == req_id:
return c
return null
func _first_folder_id() -> String:
if _game == null or _game.registry == null:
return ""
for section in _game.registry.sections.values():
if not section.folder_ids.is_empty():
return str(section.folder_ids[0])
return ""
func _finish() -> void:
if _fails == 0:
print("PASS")
else:
print("FAIL")
quit(0 if _fails == 0 else 1)
func _ok(condition: bool, message: String) -> void:
_checks += 1
if condition:
return
_fails += 1
print("FAIL: %s" % message)
func _eq(actual, expected, message: String) -> void:
_checks += 1
if actual == expected:
return
_fails += 1
print("FAIL: %s — получено %s, ожидалось %s" % [message, actual, expected])
func _fail(message: String) -> void:
_checks += 1
_fails += 1
print("FAIL: %s" % message)