Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates. Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
122 lines
6.0 KiB
Go
122 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
)
|
|
|
|
func TestDailySourceEndUsesVehicleEventTimeAfterLateDelivery(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
vin, phone, ip, code string
|
|
lateHour, lateMin int
|
|
endHour, endMin int
|
|
lateKM, endKM float64
|
|
}{
|
|
{"LGAX2BG42H1003112", "40409727033", "122.152.221.156", "dongfang_beidou", 8, 59, 23, 59, 44478.4, 44402.1},
|
|
{"LA9HE60A0PBAF4002", "13307765803", "115.231.168.135", "xinda", 20, 46, 21, 14, 6194.8, 6195.1},
|
|
} {
|
|
t.Run(tc.vin, func(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
db.SetMaxOpenConns(1)
|
|
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
|
first := time.Date(2026, 10, 21, 0, 0, 0, 0, loc)
|
|
late := time.Date(2026, 10, 21, tc.lateHour, tc.lateMin, 0, 0, loc)
|
|
last := time.Date(2026, 10, 21, tc.endHour, tc.endMin, 0, 0, loc)
|
|
parsed := func(km float64) string { return fmt.Sprintf(`{"jt808.location.total_mileage_km":%f}`, km) }
|
|
mock.ExpectQuery("(?s)SELECT vin.*MIN\\(CAST\\(event_time AS BIGINT\\)\\).*MAX\\(CAST\\(event_time AS BIGINT\\)\\)").
|
|
WillReturnRows(sqlmock.NewRows([]string{"vin", "phone", "device", "endpoint", "first", "first_parsed", "first_raw", "last", "last_parsed", "last_raw", "count", "minimum", "maximum"}).
|
|
AddRow(tc.vin, tc.phone, "", tc.ip+":1234", first, parsed(tc.endKM-100), "", late, parsed(tc.lateKM), "", 100, first.UnixMilli(), last.UnixMilli())).RowsWillBeClosed()
|
|
mock.ExpectQuery(fmt.Sprintf("(?s)SELECT event_time, parsed_json, raw_text.*phone = '%s'.*CAST\\(event_time AS BIGINT\\) <= %d.*ORDER BY event_time DESC, ts DESC", tc.phone, last.UnixMilli())).
|
|
WillReturnRows(sqlmock.NewRows([]string{"event_time", "parsed_json", "raw_text"}).AddRow(last, parsed(tc.endKM), ""))
|
|
cfg := config{TDengineDatabase: "lingniu_vehicle_ts", Location: loc}
|
|
result, err := queryDailyLastSourceRows(context.Background(), db, cfg, envelope.ProtocolJT808, "2026-10-21")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := result[tc.vin][0]
|
|
if !got.TS.Equal(last) || math.Abs(got.TotalKM-tc.endKM) > 0.0001 || got.RawSampleCount != 100 {
|
|
t.Fatalf("late delivery replaced the vehicle day end: %+v", got)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDailySourceFirstAlsoUsesVehicleEventTime(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
|
first := time.Date(2026, 10, 21, 8, 0, 0, 0, loc)
|
|
storageFirst := first.Add(time.Hour)
|
|
last := first.Add(10 * time.Hour)
|
|
mock.ExpectQuery("(?s)SELECT vin.*MIN\\(CAST").WillReturnRows(sqlmock.NewRows([]string{"vin", "phone", "device", "endpoint", "first", "first_parsed", "first_raw", "last", "last_parsed", "last_raw", "count", "minimum", "maximum"}).
|
|
AddRow("VIN", "phone", "", "1.2.3.4:123", storageFirst, `{"jt808.location.total_mileage_km":101}`, "", last, `{"jt808.location.total_mileage_km":110}`, "", 5, first.UnixMilli(), last.UnixMilli()))
|
|
mock.ExpectQuery(fmt.Sprintf("(?s)SELECT event_time.*CAST\\(event_time AS BIGINT\\) >= %d", first.UnixMilli())).WillReturnRows(sqlmock.NewRows([]string{"event", "parsed", "raw"}).AddRow(first, `{"jt808.location.total_mileage_km":100}`, ""))
|
|
got, err := queryDailyLastSourceRows(context.Background(), db, config{TDengineDatabase: "db", Location: loc}, envelope.ProtocolJT808, "2026-10-21")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got["VIN"][0].FirstTotalKM != 100 || !got["VIN"][0].FirstTS.Equal(first) {
|
|
t.Fatalf("wrong first event: %+v", got)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestMissingVehicleEventBoundaryFailsBeforeWriting(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
stamp := time.Date(2026, 10, 20, 20, 0, 0, 0, time.UTC)
|
|
mock.ExpectQuery("(?s)SELECT vin.*MAX\\(CAST").WillReturnRows(sqlmock.NewRows([]string{"vin", "phone", "device", "endpoint", "last", "parsed", "raw", "count", "maximum"}).AddRow("VIN", "phone", "", "1.2.3.4:123", stamp, `{"jt808.location.total_mileage_km":100}`, "", 2, stamp.Add(time.Hour).UnixMilli()))
|
|
mock.ExpectQuery("SELECT event_time").WillReturnRows(sqlmock.NewRows([]string{"event", "parsed", "raw"}))
|
|
if _, err := queryPreviousLastSourceRows(context.Background(), db, config{TDengineDatabase: "db", Location: time.UTC}, envelope.ProtocolJT808, "2026-10-21"); err == nil {
|
|
t.Fatal("missing event boundary silently accepted")
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestZeroMileageTailDoesNotHideLastUsableVehicleEvent(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
|
first := time.Date(2026, 10, 21, 8, 0, 0, 0, loc)
|
|
last := first.Add(time.Hour)
|
|
zero := last.Add(time.Hour)
|
|
mock.ExpectQuery("(?s)SELECT vin.*MIN\\(CAST").WillReturnRows(sqlmock.NewRows([]string{"vin", "phone", "device", "endpoint", "first", "first_parsed", "first_raw", "last", "last_parsed", "last_raw", "count", "minimum", "maximum"}).AddRow("VIN", "phone", "", "1.2.3.4:123", first, `{"jt808.location.total_mileage_km":100}`, "", zero, `{"jt808.location.total_mileage_km":0}`, "", 3, first.UnixMilli(), zero.UnixMilli()))
|
|
mock.ExpectQuery("(?s)SELECT event_time.*ORDER BY event_time DESC, ts DESC").WillReturnRows(sqlmock.NewRows([]string{"event", "parsed", "raw"}).AddRow(zero, `{"jt808.location.total_mileage_km":0}`, "").AddRow(last, `{"jt808.location.total_mileage_km":110}`, ""))
|
|
got, err := queryDailyLastSourceRows(context.Background(), db, config{TDengineDatabase: "db", Location: loc}, envelope.ProtocolJT808, "2026-10-21")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got["VIN"][0].TotalKM != 110 || !got["VIN"][0].TS.Equal(last) {
|
|
t.Fatalf("non-positive frame replaced usable end: %+v", got)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|