diff --git a/vehicle-data-platform/apps/api/internal/platform/handler_test.go b/vehicle-data-platform/apps/api/internal/platform/handler_test.go index 45880d41..8cde2362 100644 --- a/vehicle-data-platform/apps/api/internal/platform/handler_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/handler_test.go @@ -49,6 +49,22 @@ func TestHandlerVehicles(t *testing.T) { } } +func TestHandlerVehiclesFiltersServiceStatus(t *testing.T) { + handler := NewHandler(NewService(NewMockStore())) + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/vehicles?limit=10&serviceStatus=degraded", nil) + handler.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String()) + } + if !strings.Contains(rec.Body.String(), "LB9A32A24R0LS1426") { + t.Fatalf("degraded vehicles should include partially online vehicle: %s", rec.Body.String()) + } + if strings.Contains(rec.Body.String(), "LMRKH9AC2R1004087") { + t.Fatalf("degraded vehicles should exclude healthy vehicle: %s", rec.Body.String()) + } +} + func TestHandlerVehicleCoverage(t *testing.T) { handler := NewHandler(NewService(NewMockStore())) rec := httptest.NewRecorder() diff --git a/vehicle-data-platform/apps/api/internal/platform/mock_store.go b/vehicle-data-platform/apps/api/internal/platform/mock_store.go index dd387a5a..f41079de 100644 --- a/vehicle-data-platform/apps/api/internal/platform/mock_store.go +++ b/vehicle-data-platform/apps/api/internal/platform/mock_store.go @@ -61,10 +61,14 @@ func (m *MockStore) DashboardSummary(context.Context) (DashboardSummary, error) func (m *MockStore) Vehicles(_ context.Context, query url.Values) (Page[VehicleRow], error) { items := filterVehicles(m.vehicles, query) - for index := range items { - items[index].ServiceStatus = m.vehicleRowServiceStatus(items[index]) + enriched := make([]VehicleRow, 0, len(items)) + for _, item := range items { + item.ServiceStatus = m.vehicleRowServiceStatus(item) + if keepServiceStatus(item.ServiceStatus, query.Get("serviceStatus")) { + enriched = append(enriched, item) + } } - return page(items, query), nil + return page(enriched, query), nil } func (m *MockStore) vehicleRowServiceStatus(row VehicleRow) *VehicleServiceStatus { diff --git a/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go b/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go index d1d2ec16..a1170722 100644 --- a/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go +++ b/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go @@ -27,6 +27,16 @@ func buildVehicleListSQL(query url.Values) SQLQuery { where = append(where, "s.protocol = ?") args = append(args, protocol) } + switch strings.TrimSpace(query.Get("serviceStatus")) { + case "identity_required": + where = append(where, "(b.vin IS NULL OR b.vin = '')") + case "offline": + where = append(where, "b.vin IS NOT NULL", "b.vin <> ''", "COALESCE(vs.source_count, 0) > 0", "COALESCE(vs.online_source_count, 0) = 0") + case "degraded": + where = append(where, "b.vin IS NOT NULL", "b.vin <> ''", "vs.source_count > 0", "vs.online_source_count > 0", "vs.online_source_count < vs.source_count") + case "healthy": + where = append(where, "b.vin IS NOT NULL", "b.vin <> ''", "vs.source_count > 0", "vs.online_source_count = vs.source_count") + } countArgs := append([]any(nil), args...) args = append(args, limit, offset) fromSQL := `FROM vehicle_realtime_snapshot s ` + diff --git a/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go b/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go index 9d579842..9bc3944d 100644 --- a/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go @@ -26,6 +26,23 @@ func TestBuildVehicleListSQL(t *testing.T) { } } +func TestBuildVehicleListSQLFiltersServiceStatus(t *testing.T) { + query := url.Values{"serviceStatus": {"degraded"}, "limit": {"8"}} + built := buildVehicleListSQL(query) + for _, want := range []string{ + "vs.source_count > 0", + "vs.online_source_count > 0", + "vs.online_source_count < vs.source_count", + } { + if !strings.Contains(built.Text, want) { + t.Fatalf("SQL missing vehicle list service status predicate %q: %s", want, built.Text) + } + } + if !strings.Contains(built.CountText, "vs.source_count > 0") || !strings.Contains(built.CountText, "vs.online_source_count < vs.source_count") { + t.Fatalf("count SQL should include vehicle list service status predicate: %s", built.CountText) + } +} + func TestBuildVehicleCoverageSQL(t *testing.T) { query := url.Values{"keyword": {"粤A"}, "protocol": {"GB32960"}, "coverage": {"multi"}, "online": {"online"}, "bindingStatus": {"bound"}, "limit": {"8"}, "offset": {"16"}} built := buildVehicleCoverageSQL(query)