class_name TestBrowserAdapter extends GdUnitTestSuite ## Unit tests for browser_adapter.gd (T-1133, D-254 §4). ## ## Scope: the pure translation-layer logic between Oscar's T-1131 ## BrowseRequest/BrowseResponse wire contract and the browser screens' view- ## models — kind metadata, index row extraction/filtering, and detail ## payload unwrap + per-kind field mapping. All exercised with plain ## Dictionary literals standing in for decoded msgpack (no live server, no ## SimBridge, no protocol.gd round-trip needed to cover this logic). ## ## Deliberately NOT covered here: the actual wire encode/decode round-trip ## (that is protocol.gd's own concern — encode_browse_request/ ## browse_response_from_raw), and the screens' rendering of the adapter's ## output into ImplantPanel components (covered by the live make atlas ## verification per the ticket's tiering — the screens are thin renderers ## over what this adapter already validates). const SCRIPT_PATH := "res://ui/implant/apps/browser/browser_adapter.gd" func _adapter(): return load(SCRIPT_PATH) # ============================================================================= # Kind metadata # ============================================================================= func test_kind_order_has_six_entries() -> void: var a = _adapter() assert_int(a.KIND_ORDER.size()).is_equal(6) func test_kind_order_matches_d254_v1_list_order() -> void: # D-254 §4's v1 entity scope list order: star systems, bodies, stations, # corporations, commodities, trait catalog. var a = _adapter() assert_array(a.KIND_ORDER).is_equal( [ a.KIND_STAR_SYSTEM, a.KIND_BODY, a.KIND_STATION, a.KIND_CORPORATION, a.KIND_COMMODITY, a.KIND_TRAIT_TEMPLATE, ] ) func test_is_valid_kind_accepts_all_six() -> void: var a = _adapter() for kind: String in a.KIND_ORDER: assert_bool(a.is_valid_kind(kind)).override_failure_message( "%s should be a valid kind" % kind ).is_true() func test_is_valid_kind_rejects_unknown_string() -> void: var a = _adapter() assert_bool(a.is_valid_kind("Spaceship")).is_false() assert_bool(a.is_valid_kind("")).is_false() func test_kind_label_every_kind_has_a_non_empty_label() -> void: var a = _adapter() for kind: String in a.KIND_ORDER: assert_str(a.kind_label(kind)).override_failure_message( "%s must have a display label" % kind ).is_not_empty() func test_kind_label_unknown_kind_falls_back_to_the_kind_string() -> void: var a = _adapter() assert_str(a.kind_label("Nonsense")).is_equal("Nonsense") # ============================================================================= # index_rows — Ready vs. non-Ready responses # ============================================================================= func test_index_rows_ready_status_extracts_rows() -> void: var a = _adapter() var response := { "kind": "StarSystem", "status": "Ready", "index": [ {"id": "GJ-1", "primary": "GJ-1", "secondary": "K-type"}, {"id": "GJ-2", "primary": "GJ-2", "secondary": "M-type"}, ], } var rows: Array = a.index_rows(response) assert_int(rows.size()).is_equal(2) assert_str(rows[0]["id"]).is_equal("GJ-1") assert_str(rows[0]["primary"]).is_equal("GJ-1") assert_str(rows[0]["secondary"]).is_equal("K-type") func test_index_rows_missing_secondary_defaults_to_empty_string_not_null() -> void: var a = _adapter() var response := { "kind": "TraitTemplate", "status": "Ready", "index": [{"id": "tag_1", "primary": "Some Trait"}], } var rows: Array = a.index_rows(response) assert_str(rows[0]["secondary"]).is_equal("") func test_index_rows_error_status_returns_empty_array() -> void: var a = _adapter() var response := {"kind": "Body", "status": "Error", "index": null} assert_array(a.index_rows(response)).is_empty() func test_index_rows_not_found_status_returns_empty_array() -> void: # D-254/Oscar's contract: Index requests never produce NotFound in # practice, but the adapter must not crash or misbehave if one somehow # arrives — treat any non-Ready status uniformly as "no rows". var a = _adapter() var response := {"kind": "Body", "status": "NotFound", "index": null} assert_array(a.index_rows(response)).is_empty() func test_index_rows_ready_but_index_field_not_an_array_returns_empty() -> void: var a = _adapter() var response := {"kind": "Body", "status": "Ready", "index": null} assert_array(a.index_rows(response)).is_empty() func test_index_rows_ready_with_genuinely_empty_list_returns_empty_array() -> void: # The "unknown body -> Ready with empty list" convention (Oscar's # message) must round-trip cleanly, not be conflated with an error. var a = _adapter() var response := {"kind": "Body", "status": "Ready", "index": []} assert_array(a.index_rows(response)).is_empty() # ============================================================================= # filter_rows — live search box # ============================================================================= func _sample_rows() -> Array: return [ {"id": "gate-corporation", "primary": "Gate Corporation", "secondary": "corporation"}, {"id": "vethara", "primary": "Vethara", "secondary": "combine"}, {"id": "free-traders", "primary": "Free Traders Syndic", "secondary": "syndic"}, ] func test_filter_rows_empty_query_returns_all_rows_unchanged() -> void: var a = _adapter() var rows := _sample_rows() assert_array(a.filter_rows(rows, "")).is_equal(rows) func test_filter_rows_matches_primary_case_insensitively() -> void: var a = _adapter() var filtered: Array = a.filter_rows(_sample_rows(), "vethara") assert_int(filtered.size()).is_equal(1) assert_str(filtered[0]["id"]).is_equal("vethara") func test_filter_rows_matches_secondary_column() -> void: var a = _adapter() var filtered: Array = a.filter_rows(_sample_rows(), "syndic") assert_int(filtered.size()).is_equal(1) assert_str(filtered[0]["id"]).is_equal("free-traders") func test_filter_rows_matches_id() -> void: var a = _adapter() var filtered: Array = a.filter_rows(_sample_rows(), "gate-corp") assert_int(filtered.size()).is_equal(1) assert_str(filtered[0]["id"]).is_equal("gate-corporation") func test_filter_rows_no_match_returns_empty_array() -> void: var a = _adapter() assert_array(a.filter_rows(_sample_rows(), "nonexistent-zzz")).is_empty() func test_filter_rows_partial_word_matches_across_multiple_rows() -> void: var a = _adapter() # "corp" is a substring of both "Gate Corporation" (primary) and # "corporation" (secondary) — same row matches twice, must not duplicate. var filtered: Array = a.filter_rows(_sample_rows(), "corp") assert_int(filtered.size()).is_equal(1) # ============================================================================= # response_status / response_error # ============================================================================= func test_response_status_ready() -> void: var a = _adapter() assert_str(a.response_status({"status": "Ready"})).is_equal("Ready") func test_response_status_not_a_dictionary_returns_empty_string() -> void: var a = _adapter() assert_str(a.response_status(null)).is_equal("") assert_str(a.response_status("not a dict")).is_equal("") func test_response_error_returns_message_only_for_error_status() -> void: var a = _adapter() var response := {"status": "Error", "error": "db unavailable"} assert_str(a.response_error(response)).is_equal("db unavailable") func test_response_error_empty_for_ready_status() -> void: var a = _adapter() var response := {"status": "Ready", "error": ""} assert_str(a.response_error(response)).is_equal("") func test_response_error_empty_for_not_found_status() -> void: var a = _adapter() var response := {"status": "NotFound", "error": ""} assert_str(a.response_error(response)).is_equal("") # ============================================================================= # detail_payload — BrowseDetail enum-variant unwrap # ============================================================================= func test_detail_payload_unwraps_kind_keyed_variant() -> void: var a = _adapter() var response := { "kind": "Commodity", "status": "Ready", "detail": {"Commodity": {"commodity_id": "fusion_fuel", "name": "FUSION FUEL"}}, } var payload: Dictionary = a.detail_payload(response) assert_str(payload.get("commodity_id", "")).is_equal("fusion_fuel") assert_str(payload.get("name", "")).is_equal("FUSION FUEL") func test_detail_payload_non_ready_status_returns_empty_dict() -> void: var a = _adapter() var response := {"kind": "Commodity", "status": "NotFound", "detail": null} assert_that(a.detail_payload(response)).is_equal({}) func test_detail_payload_detail_field_not_a_dictionary_returns_empty_dict() -> void: var a = _adapter() var response := {"kind": "Commodity", "status": "Ready", "detail": null} assert_that(a.detail_payload(response)).is_equal({}) func test_detail_payload_falls_back_to_single_key_map_when_kind_key_missing() -> void: # Defensive path: some enum-variant encodings may not key exactly on the # response's own "kind" string — a single-key map should still unwrap. var a = _adapter() var response := { "kind": "TraitTemplate", "status": "Ready", "detail": {"SomeOtherVariantName": {"tag": "spice_market"}}, } var payload: Dictionary = a.detail_payload(response) assert_str(payload.get("tag", "")).is_equal("spice_market") # ============================================================================= # detail_view — per-kind dispatch + field mapping # ============================================================================= func test_detail_view_star_system_maps_expected_fields() -> void: var a = _adapter() var payload := { "system_id": "GJ-1", "proper_name": "Xin Chengdu", "star_type": "K", "population": 1200000, } var view: Dictionary = a.detail_view(a.KIND_STAR_SYSTEM, payload) assert_str(view["name"]).is_equal("Xin Chengdu") assert_str(view["subtitle"]).is_equal("STAR SYSTEM") var rows: Array = view["rows"] assert_bool(rows.size() > 0).is_true() # Spot-check one row is present with the right value. var found := false for row: Dictionary in rows: if row["label"] == "star type": assert_str(row["value"]).is_equal("K") found = true assert_bool(found).override_failure_message("expected a 'star type' row").is_true() func test_detail_view_star_system_falls_back_to_system_id_when_unnamed() -> void: var a = _adapter() var payload := {"system_id": "GJ-999"} var view: Dictionary = a.detail_view(a.KIND_STAR_SYSTEM, payload) assert_str(view["name"]).is_equal("GJ-999") ## Regression test for a bug found via live verification (2026-07-17): a ## real Body row (an unnamed asteroid belt, GJ0-belt) has proper_name as a ## PRESENT key with a NULL value (msgpack encodes a SQL NULL column that ## way; Dictionary.get(key, fallback)'s fallback only fires when the key is ## ABSENT, not when it's present-and-null) — the naive ## str(p.get("proper_name", p.get("body_id", "—"))) rendered the literal ## string "" instead of falling back to body_id. Distinct from ## test_detail_view_star_system_falls_back_to_system_id_when_unnamed above, ## which only covers the ABSENT-key case (that test alone did not catch this ## bug — it needs its own present-but-null case). func test_detail_view_falls_back_correctly_when_name_field_is_present_but_null() -> void: var a = _adapter() var payload := {"body_id": "GJ0-belt", "proper_name": null, "body_type": "asteroid_belt"} var view: Dictionary = a.detail_view(a.KIND_BODY, payload) assert_str(view["name"]).is_equal("GJ0-belt") ## Same present-but-null regression, for the "distance" row's unit-suffix ## special case (str(p.get("dist_ly", "—")) + " ly" would render " ly" ## for Sol's own row, where dist_ly is a genuine NULL — distance from Earth ## to itself is meaningless, not zero). func test_detail_view_star_system_distance_row_handles_null_dist_ly() -> void: var a = _adapter() var payload := {"system_id": "GJ 0", "proper_name": "Sol", "dist_ly": null} var view: Dictionary = a.detail_view(a.KIND_STAR_SYSTEM, payload) var distance_value := "" for row: Dictionary in view["rows"]: if row["label"] == "distance": distance_value = row["value"] assert_str(distance_value).is_equal("—") ## Same present-but-null regression, for the two array-summary text-block ## builders (_presence_summary/_production_chains_summary) — a null entry ## field must not render as "" inside the summary line. func test_detail_view_corporation_presence_entry_with_null_operation_omits_it_cleanly() -> void: var a = _adapter() var payload := { "corp_id": "gate-corporation", "presence": [ {"location_id": "GJ1b-S1", "location_type": "station", "primary_operation": null} ], } var view: Dictionary = a.detail_view(a.KIND_CORPORATION, payload) assert_str(view["text"]).contains("GJ1b-S1 (station)") assert_str(view["text"]).not_contains("") func test_detail_view_body_maps_expected_fields() -> void: var a = _adapter() var payload := { "body_id": "GJ1c", "proper_name": "Chengdu Prime", "body_type": "planet", "inhabited": true, } var view: Dictionary = a.detail_view(a.KIND_BODY, payload) assert_str(view["name"]).is_equal("Chengdu Prime") assert_str(view["subtitle"]).is_equal("CELESTIAL BODY") var inhabited_value := "" for row: Dictionary in view["rows"]: if row["label"] == "inhabited": inhabited_value = row["value"] assert_str(inhabited_value).is_equal("yes") func test_detail_view_station_maps_expected_fields() -> void: var a = _adapter() var payload := { "station_id": "GJ1b-S1", "proper_name": "Horizon Station", "station_type": "horizon", } var view: Dictionary = a.detail_view(a.KIND_STATION, payload) assert_str(view["name"]).is_equal("Horizon Station") assert_str(view["subtitle"]).is_equal("STATION") func test_detail_view_corporation_maps_expected_fields() -> void: var a = _adapter() var payload := { "corp_id": "gate-corporation", "proper_name": "Gate Corporation", "corp_type": "corporation", "shadow_economy_access": false, } var view: Dictionary = a.detail_view(a.KIND_CORPORATION, payload) assert_str(view["name"]).is_equal("Gate Corporation") assert_str(view["subtitle"]).is_equal("CORPORATION") var shadow_value := "" for row: Dictionary in view["rows"]: if row["label"] == "shadow economy access": shadow_value = row["value"] assert_str(shadow_value).is_equal("no") func test_detail_view_corporation_with_no_presence_yields_empty_text() -> void: var a = _adapter() var payload := {"corp_id": "gate-corporation", "presence": []} var view: Dictionary = a.detail_view(a.KIND_CORPORATION, payload) assert_str(view["text"]).is_equal("") func test_detail_view_corporation_presence_array_summarized_into_text() -> void: # CorporationDetail.presence: Vec (Oscar's T-1131 # message, 2026-07-17) — {location_id, location_type, primary_operation}. var a = _adapter() var payload := { "corp_id": "gate-corporation", "presence": [ { "location_id": "GJ1b-S1", "location_type": "station", "primary_operation": "refining", }, {"location_id": "GJ-2", "location_type": "system", "primary_operation": ""}, ], } var view: Dictionary = a.detail_view(a.KIND_CORPORATION, payload) assert_str(view["text"]).contains("GJ1b-S1 (station) — refining") assert_str(view["text"]).contains("GJ-2 (system)") func test_detail_view_commodity_maps_expected_fields_and_description_text() -> void: var a = _adapter() var payload := { "commodity_id": "fusion_fuel", "name": "FUSION FUEL", "tier": "intermediate", "description": "Refined isotopic fuel for gate-rated drives.", } var view: Dictionary = a.detail_view(a.KIND_COMMODITY, payload) assert_str(view["name"]).is_equal("FUSION FUEL") assert_str(view["subtitle"]).is_equal("COMMODITY") assert_str(view["text"]).is_equal("Refined isotopic fuel for gate-rated drives.") func test_detail_view_commodity_with_no_description_or_chains_yields_empty_text() -> void: var a = _adapter() var payload := {"commodity_id": "fusion_fuel", "description": "", "produced_by": []} var view: Dictionary = a.detail_view(a.KIND_COMMODITY, payload) assert_str(view["text"]).is_equal("") func test_detail_view_commodity_produced_by_chains_summarized_into_text() -> void: # CommodityDetail.produced_by: Vec with nested # inputs: Vec (Oscar's T-1131 message, 2026-07-17). var a = _adapter() var payload := { "commodity_id": "fusion_fuel", "description": "Refined isotopic fuel.", "produced_by": [ { "chain_id": "fusion_refining", "output_quantity": 1.0, "inputs": [ {"input_commodity_id": "raw_hydrogen", "quantity": 3.0}, {"input_commodity_id": "catalyst", "quantity": 0.5}, ], } ], } var view: Dictionary = a.detail_view(a.KIND_COMMODITY, payload) # str() on a float keeps its decimal form (str(1.0) == "1.0", not "1") — # asserting against that honest formatting rather than a rounded guess. assert_str(view["text"]).contains("Refined isotopic fuel.") assert_str(view["text"]).contains("fusion_refining (qty 1.0)") assert_str(view["text"]).contains("3.0x raw_hydrogen") assert_str(view["text"]).contains("0.5x catalyst") func test_detail_view_trait_template_maps_expected_fields_and_cultural_description() -> void: var a = _adapter() var payload := { "tag": "spice_market", "label": "Spice Market", "corridor_pool": "heritage", "cultural_description": "A dense stall market smelling of scorched cardamom.", } var view: Dictionary = a.detail_view(a.KIND_TRAIT_TEMPLATE, payload) assert_str(view["name"]).is_equal("Spice Market") assert_str(view["subtitle"]).is_equal("TRAIT TEMPLATE") assert_str(view["text"]).is_equal("A dense stall market smelling of scorched cardamom.") func test_detail_view_unknown_kind_returns_empty_shape_not_a_crash() -> void: var a = _adapter() var view: Dictionary = a.detail_view("Nonsense", {"some": "payload"}) assert_str(view["name"]).is_equal("") assert_array(view["rows"]).is_empty() func test_detail_view_missing_field_renders_as_em_dash_placeholder() -> void: var a = _adapter() var view: Dictionary = a.detail_view(a.KIND_STATION, {"station_id": "GJ1b-S1"}) var docking_value := "" for row: Dictionary in view["rows"]: if row["label"] == "docking class": docking_value = row["value"] assert_str(docking_value).is_equal("—")