From 4ed272761ba94d99912a08e31533ee484ad3f1cd Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Mon, 11 May 2026 05:22:15 +0000 Subject: [PATCH] =?UTF-8?q?test(#1188):=20red=20=E2=80=94=20/api/packets?= =?UTF-8?q?=20must=20return=20observer=5Fiata=20per=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three tests asserting the API surfaces observer_iata on: - ungrouped /api/packets rows - grouped /api/packets?groupByHash=true rows - per-observation entries in /api/packets/{id} detail response Currently fails: the SQL joins select obs.id, obs.name but not obs.iata. Fixing the join in cmd/server/db.go is the green commit. --- cmd/server/packets_observer_iata_test.go | 121 +++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 cmd/server/packets_observer_iata_test.go diff --git a/cmd/server/packets_observer_iata_test.go b/cmd/server/packets_observer_iata_test.go new file mode 100644 index 00000000..4fe4385f --- /dev/null +++ b/cmd/server/packets_observer_iata_test.go @@ -0,0 +1,121 @@ +// Test (#1188): /api/packets response must include observer_iata per packet +// so the frontend can render the IATA inline without per-row observer lookups. +package main + +import ( + "encoding/json" + "net/http/httptest" + "testing" +) + +// TestPacketsEndpointIncludesObserverIATA asserts the ungrouped packets endpoint +// surfaces the joined observer's IATA on each packet row. +func TestPacketsEndpointIncludesObserverIATA(t *testing.T) { + _, router := setupTestServer(t) + req := httptest.NewRequest("GET", "/api/packets?limit=10", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatalf("expected 200, got %d", w.Code) + } + var body map[string]interface{} + if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { + t.Fatalf("invalid JSON: %v", err) + } + packets, ok := body["packets"].([]interface{}) + if !ok || len(packets) == 0 { + t.Fatal("expected non-empty packets array") + } + + // Seeded observers: obs1 → SJC, obs2 → SFO. At least one packet row + // must carry a non-empty observer_iata string. + gotIATA := false + for _, p := range packets { + m, _ := p.(map[string]interface{}) + if m == nil { + continue + } + if _, present := m["observer_iata"]; !present { + t.Fatalf("packet missing observer_iata field; got keys: %v", keysOfMap(m)) + } + if s, _ := m["observer_iata"].(string); s != "" { + gotIATA = true + } + } + if !gotIATA { + t.Fatalf("expected at least one packet with non-empty observer_iata (seed has SJC/SFO)") + } +} + +// TestPacketsGroupedIncludesObserverIATA asserts the grouped (groupByHash) +// view also surfaces observer_iata for the header row. +func TestPacketsGroupedIncludesObserverIATA(t *testing.T) { + _, router := setupTestServer(t) + req := httptest.NewRequest("GET", "/api/packets?groupByHash=true&limit=10", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatalf("expected 200, got %d", w.Code) + } + var body map[string]interface{} + json.Unmarshal(w.Body.Bytes(), &body) + packets, _ := body["packets"].([]interface{}) + if len(packets) == 0 { + t.Fatal("expected non-empty grouped packets") + } + gotIATA := false + for _, p := range packets { + m, _ := p.(map[string]interface{}) + if _, present := m["observer_iata"]; !present { + t.Fatalf("grouped packet missing observer_iata field; got keys: %v", keysOfMap(m)) + } + if s, _ := m["observer_iata"].(string); s != "" { + gotIATA = true + } + } + if !gotIATA { + t.Fatalf("expected at least one grouped packet with non-empty observer_iata") + } +} + +// TestPacketDetailObservationsIncludeIATA asserts /api/packets/{id} returns +// per-observation observer_iata so the detail pane can render it. +func TestPacketDetailObservationsIncludeIATA(t *testing.T) { + _, router := setupTestServer(t) + // transmission_id 1 has two observations (obs1 SJC, obs2 SFO) from seedTestData + req := httptest.NewRequest("GET", "/api/packets/1", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + if w.Code != 200 { + t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) + } + var body map[string]interface{} + json.Unmarshal(w.Body.Bytes(), &body) + obs, _ := body["observations"].([]interface{}) + if len(obs) == 0 { + t.Fatalf("expected observations in detail response; body: %s", w.Body.String()) + } + gotIATA := false + for _, o := range obs { + m, _ := o.(map[string]interface{}) + if _, present := m["observer_iata"]; !present { + t.Fatalf("observation missing observer_iata field; got keys: %v", keysOfMap(m)) + } + if s, _ := m["observer_iata"].(string); s != "" { + gotIATA = true + } + } + if !gotIATA { + t.Fatalf("expected at least one observation with non-empty observer_iata") + } +} + +func keysOfMap(m map[string]interface{}) []string { + out := make([]string, 0, len(m)) + for k := range m { + out = append(out, k) + } + return out +}