Files
settled-reach/client/tests/test_system_index.gd
T
jpmschweitzerandClaude Fable 5 845737617c feat(client): atlas roads/settlements overlays + legend + server-API data delivery (T-960, T-949)
T-960: gen_l2_roads (MaintenanceAuthority-colored polylines, rail styling, junction markers) + gen_l3_settlements (size-scaled markers, capital shape, name labels) overlays — cities render on generated bodies for the first time; left-side generation legend panel (D-226 item 3, data-driven per-overlay spec, implant component library); protocol.gd decodes road_graph/settlements + the two new response types. T-949: system_index/atlas_app/overview_screen migrated off the direct star_map_data.json read to StarMapRequest over the bridge (loading state + replay-on-connect, no silent file fallback); atlas_viewer _load_markers requests CityNamesResponse for non-Sol bodies; Sol keeps the legacy authored markers.json geometry read (D-236/T-1073, load-bearing guard). Lead fix: _send_star_map_request now carries the same guard as request_star_map — the autoload's _star_map_wanted leaked across gdUnit suites and the unguarded replay-on-CONNECTED crashed 8 pre-existing flow tests on a Nil bridge; reset_test_state clears the flag. Fixtures regenerated via gen_fixtures (road/settlement samples). Full suite 2946/2946.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 15:45:54 +02:00

118 lines
4.5 KiB
GDScript

## 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:
# request_refresh() is a no-op in test mode either way (SimBridge has no
# live connection), so this only proves the _requested/_loaded bookkeeping
# doesn't get stuck: a failed ingest must clear _requested so a later
# request_refresh() is willing to ask again (not just silently no-op
# forever because a prior request was already "in flight").
SystemIndex.ingest({"status": "Ready", "nodes": [{"system_id": "GJ 1"}]})
assert_bool(SystemIndex.is_loaded()).is_true()
SystemIndex.request_refresh() # no-op — already loaded
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()