## T-1182 tests: StepCanvasRequest/StepCanvasResponse wire codec ## (step_canvas_protocol.gd, delegated via protocol.gd) — the D-255(c) ## tagged-envelope carrier. Mirrors test_atlas_data_delivery.gd's own ## encode/decode round-trip + decode_inbound classification conventions. class_name TestStepCanvasProtocol extends GdUnitTestSuite # ============================================================================= # Encode # ============================================================================= func test_encode_step_canvas_request_carries_discriminator_and_fields() -> void: var bytes := Protocol.encode_step_canvas_request( "GJ380c", "District", Vector2i(10, 20), Vector2i(64, 64), 512 ) assert_int(bytes.size()).is_greater(0) var decoded = Messagepack.decode(bytes) assert_that(decoded.status == null).is_true() var raw: Dictionary = decoded.value assert_bool(raw.get("step_canvas")).is_true() assert_str(raw.get("body_id")).is_equal("GJ380c") assert_str(raw.get("rung")).is_equal("District") assert_that(raw.get("center")).is_equal([10, 20]) assert_that(raw.get("extent")).is_equal([64, 64]) assert_int(raw.get("min_wl_m")).is_equal(512) ## The rung is sent as a bare string (rmp_serde's unit-variant convention), ## never a raw integer — a Global request must carry the literal tag ## "Global", matching step_canvas.rs's own StepCanvasRung enum encoding. func test_encode_step_canvas_request_global_rung_is_bare_string() -> void: var bytes := Protocol.encode_step_canvas_request( "GJ380c", "Global", Vector2i.ZERO, Vector2i.ZERO, 0 ) var decoded = Messagepack.decode(bytes) assert_str(decoded.value.get("rung")).is_equal("Global") # ============================================================================= # Decode — status variants # ============================================================================= func test_step_canvas_response_from_raw_decodes_ready_status() -> void: var raw := { "body_id": "GJ380c", "rung": "District", "center": [10, 20], "extent": [64, 64], "min_wl_m": 0, "status": "Ready", "canvas": null, } var decoded = Protocol.step_canvas_response_from_raw(raw) assert_str(decoded["status"]).is_equal("Ready") assert_str(decoded["error"]).is_equal("") assert_str(decoded["rung"]).is_equal("District") assert_that(decoded["center"]).is_equal(Vector2i(10, 20)) assert_that(decoded["extent"]).is_equal(Vector2i(64, 64)) func test_step_canvas_response_from_raw_decodes_pending_status() -> void: var raw := {"body_id": "GJ380c", "rung": "Chunk", "status": "Pending"} var decoded = Protocol.step_canvas_response_from_raw(raw) assert_str(decoded["status"]).is_equal("Pending") assert_that(decoded["canvas"]).is_null() func test_step_canvas_response_from_raw_decodes_error_status() -> void: var raw := { "body_id": "GJ380c", "rung": "Region", "status": {"Error": "no heightmap"} } var decoded = Protocol.step_canvas_response_from_raw(raw) assert_str(decoded["status"]).is_equal("Error") assert_str(decoded["error"]).is_equal("no heightmap") ## Not a step-canvas response (no "rung" key) -> null, so decode_inbound's ## dispatch doesn't misroute a plain AtlasLayerResponse here. func test_step_canvas_response_from_raw_returns_null_without_rung_key() -> void: var raw := {"body_id": "GJ380c", "status": "Ready"} assert_that(Protocol.step_canvas_response_from_raw(raw)).is_null() # ============================================================================= # decode_inbound classification — the "rung" discriminator must win BEFORE # the generic "status"-only AtlasLayerResponse fallback (a step-canvas # response also carries "status"). # ============================================================================= func test_decode_inbound_classifies_step_canvas() -> void: var raw := {"body_id": "GJ380c", "rung": "District", "status": "Pending"} var encoded = Messagepack.encode(raw) var inbound: Dictionary = Protocol.decode_inbound(encoded.value) assert_str(inbound["kind"]).is_equal("step_canvas") func test_decode_inbound_still_classifies_plain_atlas_response() -> void: var raw := {"body_id": "GJ380c", "status": "Ready"} var encoded = Messagepack.encode(raw) var inbound: Dictionary = Protocol.decode_inbound(encoded.value) assert_str(inbound["kind"]).is_equal("atlas") # ============================================================================= # EncodedStepCanvas — the PNG-per-field array-of-int wire shape # (step_canvas.rs's png_bytes: Vec with NO serde_bytes anywhere in this # codebase serializes via serialize_seq, a msgpack ARRAY of ints, never a # `bin` blob — decode_png_field()/​_decode_encoded_canvas() must repack that # Array into a PackedByteArray, not expect messagepack.gd's bin_8/16/32 path). # ============================================================================= func test_decode_png_field_repacks_array_of_ints_to_packed_byte_array() -> void: # A 1x1 all-black L8 PNG's real byte stream, as it would arrive already # decoded off the wire (a plain Array of ints, one per byte) — using real # PNG magic bytes so a downstream Image.load_png_from_buffer() call # would also succeed, not just this repack step in isolation. var png_bytes := PackedByteArray([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) var as_array: Array = [] for b in png_bytes: as_array.append(b) var field_raw := {"png_bytes": as_array} var result: PackedByteArray = Protocol.step_canvas_response_from_raw( { "body_id": "GJ380c", "rung": "Chunk", "status": "Ready", "canvas": { "width": 1, "height": 1, "morphology": field_raw, "elev_q": {}, "temp_dc": {"values": []}, "moisture_q": {}, "vegetation": {}, "settlement_id": {"values": []}, "glaciation": {}, "flooded_q": {}, "courses": [], "cliffs": [], }, } )["canvas"]["morphology"] assert_that(result).is_equal(png_bytes) func test_decode_encoded_canvas_passes_through_temp_dc_and_settlement_id_as_arrays() -> void: var raw := { "body_id": "GJ380c", "rung": "Chunk", "status": "Ready", "canvas": { "width": 2, "height": 1, "morphology": {}, "elev_q": {}, "temp_dc": {"values": [120, -32768]}, "moisture_q": {}, "vegetation": {}, "settlement_id": {"values": [0, 7]}, "glaciation": {}, "flooded_q": {}, "courses": [], "cliffs": [], }, } var decoded = Protocol.step_canvas_response_from_raw(raw) var canvas: Dictionary = decoded["canvas"] assert_that(canvas["temp_dc"]).is_equal([120, -32768]) assert_that(canvas["settlement_id"]).is_equal([0, 7]) func test_decode_encoded_canvas_passes_through_courses_and_cliffs_unshaped() -> void: var courses := [{"edge_id": 1, "class": 2, "points": [[0, 0], [100, 100]], "terminus": "Mouth"}] var cliffs := [{"point": [5, 5], "channel_depth_dm": 10, "cliff_edge": true}] var raw := { "body_id": "GJ380c", "rung": "Chunk", "status": "Ready", "canvas": { "width": 1, "height": 1, "morphology": {}, "elev_q": {}, "temp_dc": {"values": []}, "moisture_q": {}, "vegetation": {}, "settlement_id": {"values": []}, "glaciation": {}, "flooded_q": {}, "courses": courses, "cliffs": cliffs, }, } var decoded = Protocol.step_canvas_response_from_raw(raw) var canvas: Dictionary = decoded["canvas"] assert_that(canvas["courses"]).is_equal(courses) assert_that(canvas["cliffs"]).is_equal(cliffs) func test_decode_png_field_malformed_input_returns_empty_packed_byte_array() -> void: assert_that(Protocol._scp().decode_png_field(null)).is_equal(PackedByteArray()) assert_that(Protocol._scp().decode_png_field({"png_bytes": "not an array"})).is_equal( PackedByteArray() ) ## PR #203 review (Hoshe finding 1) — the genuine msgpack `bin_8`/`bin_16`/ ## `bin_32` decode path. messagepack.gd's own decoder returns ## `StreamPeerBuffer.get_partial_data()`'s `[Error, PackedByteArray]` pair for ## a bin-typed field — which passes decode_png_field()'s `is Array` guard ## just as readily as the real (today's) array-of-ints shape, so this must be ## exercised with the REAL bin-shaped decode output, not a hand-built ## Dictionary, to prove the fix actually detects it. Built by round-tripping ## a genuine PackedByteArray value through Messagepack.encode()/decode() ## directly (bypassing step_canvas_protocol.gd's own encoder, which never ## sends a PackedByteArray for png_bytes today) — this is exactly the shape ## a future serde_bytes-annotated server would produce. func test_decode_png_field_handles_the_genuine_bin_type_shape() -> void: var real_png_bytes := PackedByteArray([0x89, 0x50, 0x4e, 0x47, 1, 2, 3, 4]) var encoded = Messagepack.encode(real_png_bytes) assert_that(encoded.status).is_null() var decoded = Messagepack.decode(encoded.value) assert_that(decoded.status).is_null() # Sanity: this really is the [error, data] pair shape, not the plain-array # shape the rest of this suite exercises — if messagepack.gd's own # behavior ever changes, this assertion fails loudly instead of the test # silently exercising the wrong code path. var bin_shape: Variant = decoded.value assert_bool(bin_shape is Array).is_true() assert_int((bin_shape as Array).size()).is_equal(2) assert_bool((bin_shape as Array)[0] is int).is_true() assert_bool((bin_shape as Array)[1] is PackedByteArray).is_true() var result: PackedByteArray = Protocol._scp().decode_png_field({"png_bytes": bin_shape}) assert_that(result).override_failure_message( "bin-shaped png_bytes must decode to the REAL inner bytes, not silently" + " collapse to [0, 0] via PackedByteArray([int, PackedByteArray]) coercion" ).is_equal(real_png_bytes)