229 lines
7.4 KiB
GDScript
229 lines
7.4 KiB
GDScript
extends GdUnitTestSuite
|
|
|
|
## NEO-122: `QuestProgressClient` GET parse, accept POST, and failure signals.
|
|
|
|
const QuestProgressClient := preload("res://scripts/quest_progress_client.gd")
|
|
|
|
const PROTOTYPE_QUEST_GATHER_ID := "prototype_quest_gather_intro"
|
|
|
|
var _progress_capture: Dictionary = {}
|
|
var _accept_capture: Dictionary = {}
|
|
|
|
|
|
func _capture_progress(snapshot: Dictionary) -> void:
|
|
_progress_capture = snapshot
|
|
|
|
|
|
func _capture_accept(quest_id: String, result: Dictionary) -> void:
|
|
_accept_capture = {"questId": quest_id, "result": result}
|
|
|
|
|
|
class MockHttpTransport:
|
|
extends Node
|
|
signal request_completed(
|
|
result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
|
|
)
|
|
var last_url: String = ""
|
|
var last_method: int = HTTPClient.METHOD_GET
|
|
var response_code: int = 200
|
|
var body_json: String = ""
|
|
|
|
func request(
|
|
url: String,
|
|
_custom_headers: PackedStringArray = PackedStringArray(),
|
|
method: HTTPClient.Method = HTTPClient.METHOD_GET,
|
|
_request_data: String = ""
|
|
) -> Error:
|
|
last_url = url
|
|
last_method = method
|
|
request_completed.emit(
|
|
HTTPRequest.RESULT_SUCCESS,
|
|
response_code,
|
|
PackedStringArray(),
|
|
body_json.to_utf8_buffer()
|
|
)
|
|
return OK
|
|
|
|
|
|
static func _not_started_progress_json() -> String:
|
|
return (
|
|
'{"schemaVersion":1,"playerId":"dev-local-1","quests":'
|
|
+ '[{"questId":"prototype_quest_gather_intro","status":"not_started",'
|
|
+ '"currentStepIndex":0,"objectiveCounters":{}}]}'
|
|
)
|
|
|
|
|
|
static func _active_partial_json() -> String:
|
|
return (
|
|
'{"schemaVersion":1,"playerId":"dev-local-1","quests":'
|
|
+ '[{"questId":"prototype_quest_gather_intro","status":"active",'
|
|
+ '"currentStepIndex":0,"objectiveCounters":{"gather_intro_obj_scrap":2}}]}'
|
|
)
|
|
|
|
|
|
static func _completed_json() -> String:
|
|
return (
|
|
'{"schemaVersion":1,"playerId":"dev-local-1","quests":'
|
|
+ '[{"questId":"prototype_quest_gather_intro","status":"completed",'
|
|
+ '"currentStepIndex":0,"objectiveCounters":{},'
|
|
+ '"completedAt":"2026-06-07T12:00:00Z"}]}'
|
|
)
|
|
|
|
|
|
static func _accept_success_json() -> String:
|
|
return (
|
|
'{"schemaVersion":1,"accepted":true,"quest":'
|
|
+ '{"questId":"prototype_quest_gather_intro","status":"active",'
|
|
+ '"currentStepIndex":0,"objectiveCounters":{}}}'
|
|
)
|
|
|
|
|
|
static func _accept_deny_json() -> String:
|
|
return (
|
|
'{"schemaVersion":1,"accepted":false,"reasonCode":"prerequisite_incomplete"}'
|
|
)
|
|
|
|
|
|
func _make_client(sync_transport: Node, accept_transport: Node = null) -> Node:
|
|
var c: Node = QuestProgressClient.new()
|
|
c.set("injected_sync_http", sync_transport)
|
|
var accept_http: Node = accept_transport if accept_transport != null else sync_transport
|
|
c.set("injected_accept_http", accept_http)
|
|
auto_free(sync_transport)
|
|
if accept_transport != null and accept_transport != sync_transport:
|
|
auto_free(accept_transport)
|
|
auto_free(c)
|
|
add_child(c)
|
|
return c
|
|
|
|
|
|
class NoopHttpTransport:
|
|
extends Node
|
|
signal request_completed(
|
|
result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
|
|
)
|
|
|
|
func request(
|
|
_url: String,
|
|
_custom_headers: PackedStringArray = PackedStringArray(),
|
|
_method: HTTPClient.Method = HTTPClient.METHOD_GET,
|
|
_request_data: String = ""
|
|
) -> Error:
|
|
return OK
|
|
|
|
|
|
func test_parse_not_started_quest_row() -> void:
|
|
# Arrange
|
|
var json := _not_started_progress_json()
|
|
# Act
|
|
var snapshot: Variant = QuestProgressClient.parse_quest_progress_json(json)
|
|
# Assert
|
|
assert_that(snapshot is Dictionary).is_true()
|
|
var c: Node = QuestProgressClient.new()
|
|
auto_free(c)
|
|
var row: Dictionary = c.call("quest_row", PROTOTYPE_QUEST_GATHER_ID, snapshot as Dictionary)
|
|
assert_that(str(row.get("status", ""))).is_equal("not_started")
|
|
|
|
|
|
func test_parse_active_row_has_objective_counters() -> void:
|
|
# Arrange
|
|
var json := _active_partial_json()
|
|
# Act
|
|
var snapshot: Variant = QuestProgressClient.parse_quest_progress_json(json)
|
|
# Assert
|
|
assert_that(snapshot is Dictionary).is_true()
|
|
var c: Node = QuestProgressClient.new()
|
|
auto_free(c)
|
|
var row: Dictionary = c.call("quest_row", PROTOTYPE_QUEST_GATHER_ID, snapshot as Dictionary)
|
|
assert_that(str(row.get("status", ""))).is_equal("active")
|
|
var counters: Variant = row.get("objectiveCounters", null)
|
|
assert_that(counters is Dictionary).is_true()
|
|
assert_that(int((counters as Dictionary).get("gather_intro_obj_scrap", 0))).is_equal(2)
|
|
|
|
|
|
func test_parse_completed_row_has_completed_at() -> void:
|
|
# Arrange
|
|
var json := _completed_json()
|
|
# Act
|
|
var snapshot: Variant = QuestProgressClient.parse_quest_progress_json(json)
|
|
# Assert
|
|
assert_that(snapshot is Dictionary).is_true()
|
|
var c: Node = QuestProgressClient.new()
|
|
auto_free(c)
|
|
var row: Dictionary = c.call("quest_row", PROTOTYPE_QUEST_GATHER_ID, snapshot as Dictionary)
|
|
assert_that(str(row.get("status", ""))).is_equal("completed")
|
|
assert_that(str(row.get("completedAt", ""))).contains("2026-06-07")
|
|
|
|
|
|
func test_request_sync_emits_quest_progress_received() -> void:
|
|
# Arrange
|
|
_progress_capture = {}
|
|
var transport := MockHttpTransport.new()
|
|
transport.body_json = _not_started_progress_json()
|
|
var noop := NoopHttpTransport.new()
|
|
var c := _make_client(transport, noop)
|
|
c.connect("quest_progress_received", Callable(self, "_capture_progress"))
|
|
monitor_signals(c)
|
|
# Act
|
|
c.call("request_sync_from_server")
|
|
# Assert
|
|
await assert_signal(c).is_emitted("quest_progress_received", any())
|
|
assert_that(_progress_capture.get("playerId", "")).is_equal("dev-local-1")
|
|
assert_that(transport.last_url).contains("/quest-progress")
|
|
|
|
|
|
func test_http_404_emits_quest_sync_failed() -> void:
|
|
# Arrange
|
|
var transport := MockHttpTransport.new()
|
|
transport.response_code = 404
|
|
var noop := NoopHttpTransport.new()
|
|
var c := _make_client(transport, noop)
|
|
monitor_signals(c)
|
|
# Act
|
|
c.call("request_sync_from_server")
|
|
# Assert
|
|
await assert_signal(c).is_emitted("quest_sync_failed", "HTTP 404 (player unknown)")
|
|
|
|
|
|
func test_parse_quest_progress_json_returns_null_for_schema_mismatch() -> void:
|
|
# Arrange
|
|
var json := '{"schemaVersion":2,"playerId":"dev-local-1","quests":[]}'
|
|
# Act
|
|
var snapshot: Variant = QuestProgressClient.parse_quest_progress_json(json)
|
|
# Assert
|
|
assert_that(snapshot).is_null()
|
|
|
|
|
|
func test_request_accept_emits_quest_accept_result_received() -> void:
|
|
# Arrange
|
|
_accept_capture = {}
|
|
var transport := MockHttpTransport.new()
|
|
transport.body_json = _accept_success_json()
|
|
var noop := NoopHttpTransport.new()
|
|
var c := _make_client(noop, transport)
|
|
c.connect("quest_accept_result_received", Callable(self, "_capture_accept"))
|
|
# Act
|
|
var started: bool = bool(c.call("request_accept", PROTOTYPE_QUEST_GATHER_ID))
|
|
# Assert
|
|
assert_that(started).is_true()
|
|
assert_that(_accept_capture.get("questId", "")).is_equal(PROTOTYPE_QUEST_GATHER_ID)
|
|
assert_that(bool((_accept_capture.get("result", {}) as Dictionary).get("accepted", false))).is_true()
|
|
assert_that(transport.last_url).contains("/accept")
|
|
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
|
|
|
|
|
func test_request_accept_emits_deny_with_reason_code() -> void:
|
|
# Arrange
|
|
_accept_capture = {}
|
|
var transport := MockHttpTransport.new()
|
|
transport.body_json = _accept_deny_json()
|
|
var noop := NoopHttpTransport.new()
|
|
var c := _make_client(noop, transport)
|
|
c.connect("quest_accept_result_received", Callable(self, "_capture_accept"))
|
|
# Act
|
|
c.call("request_accept", "prototype_quest_refine_intro")
|
|
# Assert
|
|
var result: Dictionary = _accept_capture.get("result", {}) as Dictionary
|
|
assert_that(bool(result.get("accepted", true))).is_false()
|
|
assert_that(str(result.get("reasonCode", ""))).is_equal("prerequisite_incomplete")
|