H1 demux: ShapeProbe defensive multi-shape rejection (union frames now Err, not first-match; +2 tests) and doc claim made honest. H2/T1 SystemIndex.reset_test_state() folded into SimBridge.reset_test_state() (load() inline per autoload rule) + has_pending_request() accessor. H3 no-op tests now assert the replay flag both directions. H4 retry test actually ingests a failure and asserts the retry semantic. H5 error fixture uses the normalized status string. H6 bridge_tcp e2e sends all five frame shapes over real TCP (star-map + city-names buffers asserted). H7 positive replay-on-CONNECTED test via the test_local_bridge test-mode-flip precedent (stub bridge captures + decodes the request bytes). H8/T2 stale PLACEHOLDER doc replaced with the confirmed contract. H9 is_capital doc matches the COALESCE reality. T-r1 demux ceiling written down (next shape = tagged envelope). T-r2 AtlasLayerResponse governance ceiling comment. Lead item: the four cargo-fmt-formatted files from the gate round are now committed (layer_proxy/plugin/bridge-mod/main). H10 note for the record: the 13 snapshot_*.msgpack fixtures in commit 845737617 were regenerated because they were stale against their own generator (pre-existing version-key removal) — verified harmless, no client reads that key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
127 lines
4.9 KiB
GDScript
127 lines
4.9 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:
|
|
# 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()
|