## T-949 tests: SystemIndex — the cached StarMapResponse reader that replaces ## the direct star_map_data.json FileAccess read (atlas_app.gd's Reach screen, ## economics OverviewScreen). ## ## Fixtures use {"status": "Ready", "nodes": [...]} — the shape ## protocol.gd's star_map_response_from_raw hands to ingest() AFTER unwrapping ## the real StarMapResponse's status/data envelope (dudley-atlas-server ## contract, 2026-07-14); SystemIndex itself never sees "data" directly. ## ## SystemIndex's cache is process-global (static var — there is no per-call ## instance), so these tests are written to be order-independent: every test ## calls ingest() with its own "Ready" fixture first, which fully OVERWRITES ## the cache rather than appending, so prior test pollution can't leak into ## an assertion (see system_index.gd's ingest()). class_name TestSystemIndex extends GdUnitTestSuite func test_ingest_sorts_by_proper_name() -> void: SystemIndex.ingest( { "status": "Ready", "nodes": [ {"system_id": "GJ 71", "proper_name": "Tau Ceti"}, {"system_id": "GJ 0", "proper_name": "Sol"}, {"system_id": "GJ 903", "proper_name": "Aldrin"}, ] } ) var sorted: Array = SystemIndex.get_sorted_systems() assert_int(sorted.size()).is_equal(3) assert_str(sorted[0].get("proper_name")).is_equal("Aldrin") assert_str(sorted[1].get("proper_name")).is_equal("Sol") assert_str(sorted[2].get("proper_name")).is_equal("Tau Ceti") func test_ingest_falls_back_to_system_id_when_proper_name_missing() -> void: SystemIndex.ingest( { "status": "Ready", "nodes": [ {"system_id": "GJ 999"}, {"system_id": "GJ 001"}, ] } ) var sorted: Array = SystemIndex.get_sorted_systems() assert_int(sorted.size()).is_equal(2) assert_str(sorted[0].get("system_id")).is_equal("GJ 001") assert_str(sorted[1].get("system_id")).is_equal("GJ 999") func test_ingest_drops_nodes_with_empty_system_id() -> void: SystemIndex.ingest( { "status": "Ready", "nodes": [ {"system_id": "GJ 71", "proper_name": "Tau Ceti"}, {"proper_name": "No id"}, {"system_id": "", "proper_name": "Empty id"}, ] } ) var sorted: Array = SystemIndex.get_sorted_systems() assert_int(sorted.size()).is_equal(1) assert_str(sorted[0].get("system_id")).is_equal("GJ 71") func test_ingest_marks_loaded() -> void: SystemIndex.ingest({"status": "Ready", "nodes": [{"system_id": "GJ 71"}]}) assert_bool(SystemIndex.is_loaded()).is_true() func test_ingest_overwrites_not_appends() -> void: SystemIndex.ingest( {"status": "Ready", "nodes": [{"system_id": "GJ 1"}, {"system_id": "GJ 2"}]} ) assert_int(SystemIndex.get_sorted_systems().size()).is_equal(2) SystemIndex.ingest({"status": "Ready", "nodes": [{"system_id": "GJ 3"}]}) # A second StarMapResponse (e.g. a reconnect) replaces the cache wholesale # rather than accumulating duplicates. assert_int(SystemIndex.get_sorted_systems().size()).is_equal(1) assert_str(SystemIndex.get_sorted_systems()[0].get("system_id")).is_equal("GJ 3") func test_ingest_ignores_non_ready_status() -> void: # Seed a known-good cache first... SystemIndex.ingest({"status": "Ready", "nodes": [{"system_id": "GJ 1"}]}) # ...an Error response (protocol.gd already reduces it to nodes=[]) must # NOT wipe the cache down to empty — the last good data survives. SystemIndex.ingest({"status": "Error", "error": "db unavailable", "nodes": []}) assert_int(SystemIndex.get_sorted_systems().size()).override_failure_message( "a non-Ready StarMapResponse must not overwrite the cache" ).is_equal(1) assert_str(SystemIndex.get_sorted_systems()[0].get("system_id")).is_equal("GJ 1") func test_request_refresh_retries_after_a_failed_ingest() -> void: # The retry semantic itself (review H4): a failed ingest must clear the # in-flight flag so a later request_refresh() actually asks again — not # silently no-op forever because a prior request was "in flight". SystemIndex.reset_test_state() SystemIndex.request_refresh() assert_bool(SystemIndex.has_pending_request()).is_true() assert_bool(SystemIndex.is_loaded()).is_false() # A failed fetch arrives: stays unloaded, and the in-flight flag clears... SystemIndex.ingest({"status": "Error", "error": "db unavailable", "nodes": []}) assert_bool(SystemIndex.has_pending_request()).override_failure_message( "a failed ingest must clear _requested so a later refresh can retry" ).is_false() assert_bool(SystemIndex.is_loaded()).is_false() # ...so a retry genuinely re-requests instead of no-opping. SystemIndex.request_refresh() assert_bool(SystemIndex.has_pending_request()).is_true() SystemIndex.reset_test_state() func test_request_refresh_does_not_crash_when_already_loaded() -> void: SystemIndex.ingest({"status": "Ready", "nodes": [{"system_id": "GJ 71"}]}) # Idempotent no-op once loaded — must not error (SimBridge is in # test_mode, so request_star_map() would no-op regardless). SystemIndex.request_refresh() assert_bool(SystemIndex.is_loaded()).is_true()