Files
code_of_conquest_dnd/client/tests/unit/test_npc_service.gd
2026-07-10 12:55:52 -05:00

93 lines
3.1 KiB
GDScript

# client/tests/unit/test_npc_service.gd
extends "res://addons/gut/test.gd"
const NpcService = preload("res://scripts/net/npc_service.gd")
const DmResponse = preload("res://scripts/net/dm_response.gd")
const FallbackLibrary = preload("res://scripts/net/fallback_library.gd")
const FakeDmTransport = preload("res://tests/doubles/fake_transport.gd")
var _fallback
func before_each():
_fallback = FallbackLibrary.new(
"res://content/fallback/missing.json", "res://content/fallback/missing.json")
func _svc(resp) -> Dictionary:
var t = FakeDmTransport.new()
t.set_response(resp)
return {"svc": NpcService.new(t, _fallback), "transport": t}
func _speak(pair, available):
return await pair["svc"].speak("fenn", "what happened?", CanonLog.new(), -10, available)
func test_posts_full_request_body():
var pair = _svc(DmResponse.ok(200, {"prose": "Aye."}))
await _speak(pair, ["refuse"])
assert_eq(pair["transport"].last_path, "/npc/speak")
var body = pair["transport"].last_body
assert_eq(body["npc_id"], "fenn")
assert_eq(body["disposition"], -10)
assert_eq(body["utterance"], "what happened?")
assert_true(body.has("available_moves"))
assert_true(body.has("canon_log"))
func test_valid_move_kept_prose_cleaned():
var prose = "Aye, I knew them. [MOVE: reveal(varrell_twins)]"
var pair = _svc(DmResponse.ok(200, {"prose": prose}))
var r = await _speak(pair, ["reveal(varrell_twins)", "refuse"])
assert_false(r.degraded)
assert_eq(r.display_text, "Aye, I knew them.")
assert_eq(r.valid_moves.size(), 1)
assert_eq(r.dropped_moves.size(), 0)
func test_illegal_move_dropped_prose_kept():
var prose = "No. [MOVE: offer_quest(some_other)]"
var pair = _svc(DmResponse.ok(200, {"prose": prose}))
var r = await _speak(pair, ["offer_quest(find_the_ledger)", "refuse"])
assert_eq(r.display_text, "No.")
assert_eq(r.valid_moves.size(), 0)
assert_eq(r.dropped_moves.size(), 1)
func test_adjust_disposition_alias_is_validated():
var prose = "Fine. [ADJUST_DISPOSITION: +5]"
var pair = _svc(DmResponse.ok(200, {"prose": prose}))
var r = await _speak(pair, ["adjust_disposition"])
assert_eq(r.valid_moves.size(), 1)
assert_eq(r.valid_moves[0]["name"], "adjust_disposition")
assert_eq(r.valid_moves[0]["args"], ["5"])
func test_end_conversation_sets_flag():
var prose = "We're done. [MOVE: end_conversation()]"
var pair = _svc(DmResponse.ok(200, {"prose": prose}))
var r = await _speak(pair, ["end_conversation"])
assert_true(r.ends_conversation)
func test_facts_harvested():
var prose = "The bridge fell. [FACT: the eastern bridge is out]"
var pair = _svc(DmResponse.ok(200, {"prose": prose}))
var r = await _speak(pair, ["refuse"])
assert_eq(r.facts, ["the eastern bridge is out"])
func test_502_degrades_no_moves():
var pair = _svc(DmResponse.ok(502, {"detail": {"model_error": "boom"}}))
var r = await _speak(pair, ["refuse"])
assert_true(r.degraded)
assert_eq(r.display_text, FallbackLibrary.NPC_LAST_RESORT)
assert_eq(r.valid_moves, [])
func test_transport_failure_degrades():
var pair = _svc(DmResponse.failed("refused"))
var r = await _speak(pair, ["refuse"])
assert_true(r.degraded)