diff --git a/vehicle-data-platform/apps/api/internal/platform/production_store.go b/vehicle-data-platform/apps/api/internal/platform/production_store.go index 228f8925..93231a1c 100644 --- a/vehicle-data-platform/apps/api/internal/platform/production_store.go +++ b/vehicle-data-platform/apps/api/internal/platform/production_store.go @@ -685,11 +685,17 @@ func (s *ProductionStore) RawFrames(ctx context.Context, query RawFrameQuery) (P total := 0 if built.CountText != "" { if err := s.tdengine.QueryRowContext(ctx, built.CountText, built.CountArgs...).Scan(&total); err != nil { + if isTDengineTableNotExist(err) { + return Page[RawFrameRow]{Items: []RawFrameRow{}, Total: 0, Limit: query.Limit, Offset: query.Offset}, nil + } return Page[RawFrameRow]{}, err } } rows, err := s.tdengine.QueryContext(ctx, built.Text, built.Args...) if err != nil { + if isTDengineTableNotExist(err) { + return Page[RawFrameRow]{Items: []RawFrameRow{}, Total: 0, Limit: query.Limit, Offset: query.Offset}, nil + } return Page[RawFrameRow]{}, err } defer rows.Close() diff --git a/vehicle-data-platform/apps/api/internal/platform/production_store_test.go b/vehicle-data-platform/apps/api/internal/platform/production_store_test.go index f0af74ac..85d4eb2c 100644 --- a/vehicle-data-platform/apps/api/internal/platform/production_store_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/production_store_test.go @@ -1,11 +1,57 @@ package platform import ( + "context" + "database/sql" + "database/sql/driver" "errors" + "io" "strings" "testing" ) +func init() { + sql.Register("tdengine_missing_table_test", missingTableDriver{}) +} + +type missingTableDriver struct{} + +func (missingTableDriver) Open(_ string) (driver.Conn, error) { + return missingTableConn{}, nil +} + +type missingTableConn struct{} + +func (missingTableConn) Prepare(_ string) (driver.Stmt, error) { + return nil, errors.New("not implemented") +} + +func (missingTableConn) Close() error { + return nil +} + +func (missingTableConn) Begin() (driver.Tx, error) { + return nil, errors.New("not implemented") +} + +func (missingTableConn) QueryContext(_ context.Context, _ string, _ []driver.NamedValue) (driver.Rows, error) { + return nil, errors.New("[0x2603] Fail to get table info, error: Table does not exist") +} + +type emptyRows struct{} + +func (emptyRows) Columns() []string { + return []string{} +} + +func (emptyRows) Close() error { + return nil +} + +func (emptyRows) Next(_ []driver.Value) error { + return io.EOF +} + func TestBuildVehicleServiceOverviewBatchSQLUsesFuzzyKeywordMatching(t *testing.T) { built := buildVehicleServiceOverviewBatchSQL(VehicleOverviewBatchQuery{ Keywords: []string{"AG183", "R0LS1426"}, @@ -35,6 +81,32 @@ func TestTDengineTableNotExistErrorIsRecognized(t *testing.T) { } } +func TestRawFramesReturnsEmptyPageWhenTDengineTableIsMissing(t *testing.T) { + tdengine, err := sql.Open("tdengine_missing_table_test", "") + if err != nil { + t.Fatalf("open test tdengine: %v", err) + } + defer tdengine.Close() + store := &ProductionStore{tdengine: tdengine, tdDatabase: "vehicle_test"} + + page, err := store.RawFrames(context.Background(), RawFrameQuery{ + Protocol: "GB32960", + VIN: "VIN_WITHOUT_SUBTABLE", + Limit: 20, + Offset: 40, + IncludeFields: true, + }) + if err != nil { + t.Fatalf("missing raw frame table should return an empty page, got error: %v", err) + } + if page.Total != 0 || len(page.Items) != 0 || page.Limit != 20 || page.Offset != 40 { + t.Fatalf("missing raw frame table should preserve pagination with empty items, got %+v", page) + } + if page.Items == nil { + t.Fatalf("missing raw frame table should return a non-nil empty item list") + } +} + func TestVehicleServiceOverviewMatchesPartialKeyword(t *testing.T) { overview := VehicleServiceOverview{ VIN: "LB9A32A24R0LS1426",