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.
133 lines
5.6 KiB
Go
133 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
mysql "github.com/go-sql-driver/mysql"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
)
|
|
|
|
// A lost session must never reconnect without its temporary shadow tables.
|
|
type oneSessionConnector struct {
|
|
driver.Connector
|
|
used bool
|
|
}
|
|
|
|
func (c *oneSessionConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
|
if c.used {
|
|
return nil, errors.New("temporary-table integration session cannot reconnect")
|
|
}
|
|
c.used = true
|
|
return c.Connector.Connect(ctx)
|
|
}
|
|
|
|
func TestProtectedGPSWriteMySQLTemporaryTables(t *testing.T) {
|
|
dsn := os.Getenv("BACKFILL_TEST_MYSQL_DSN")
|
|
if dsn == "" {
|
|
t.Skip("set BACKFILL_TEST_MYSQL_DSN for isolated temporary-table integration")
|
|
}
|
|
cfg, err := mysql.ParseDSN(dsn)
|
|
if err != nil {
|
|
t.Fatal("invalid integration DSN")
|
|
}
|
|
cfg.ParseTime = true
|
|
cfg.Loc = time.FixedZone("Asia/Shanghai", 8*3600)
|
|
connector, err := mysql.NewConnector(cfg)
|
|
if err != nil {
|
|
t.Fatal("integration connector unavailable")
|
|
}
|
|
db := sql.OpenDB(&oneSessionConnector{Connector: connector})
|
|
db.SetMaxOpenConns(1)
|
|
db.SetMaxIdleConns(1)
|
|
defer db.Close()
|
|
ctx := context.Background()
|
|
for _, table := range []string{"vehicle_daily_mileage_source", "vehicle_daily_mileage", "vehicle_data_source"} {
|
|
if _, err := db.ExecContext(ctx, "CREATE TEMPORARY TABLE gps_fixture_"+table+" LIKE "+table); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := db.ExecContext(ctx, "CREATE TEMPORARY TABLE "+table+" LIKE gps_fixture_"+table); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
loc := cfg.Loc
|
|
prev := time.Date(2026, 10, 20, 23, 59, 0, 0, loc)
|
|
end := time.Date(2026, 10, 21, 23, 59, 0, 0, loc)
|
|
const vin = "LTESTGPSPROTECT001"
|
|
key := "JT808:fixture@PLATFORM:g7s"
|
|
if _, err := db.Exec(`INSERT INTO vehicle_daily_mileage_source
|
|
(vin,stat_date,protocol,source_key,source_ip,source_endpoint,phone,platform_name,first_total_mileage_km,latest_total_mileage_km,daily_mileage_km,sample_count,first_event_time,latest_event_time,quality_status,quality_reason)
|
|
VALUES (?,'2026-10-21','JT808',?,'1.2.3.4','1.2.3.4:1','fixture','G7s',100,120,20,200,?,?,'OK','historical_source_baseline'),
|
|
(?,'2026-10-21','JT808','JT808:other@PLATFORM:other','2.3.4.5','2.3.4.5:2','other','Other',100,101,1,3,?,?,'OK','historical_source_baseline')`, vin, key, prev, end, vin, prev, end.Add(-time.Minute)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Keep the existing manual final selected. This exercises the protected
|
|
// write path without MySQL's temporary-table self-join restriction in the
|
|
// independent projection query (covered separately by replay verification).
|
|
if _, err := db.Exec(`INSERT INTO vehicle_daily_mileage_source (vin,stat_date,protocol,source_key,source_ip,daily_mileage_km,sample_count,quality_status,quality_reason,is_selected)
|
|
VALUES (?,'2026-10-21','JT808','manual-report','manual-import',88,1,'OK','manual_gps_report',1)`, vin); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := db.Exec(`INSERT INTO vehicle_daily_mileage (vin,stat_date,protocol,source_id,daily_mileage_km) VALUES (?,'2026-10-21','JT808',0,88)`, vin); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
agg := &metricAgg{VIN: vin, Date: "2026-10-21", Protocol: envelope.ProtocolJT808, SourceKey: key, Phone: "fixture", SourceEndpoint: "1.2.3.4:3", SourceCode: "g7s", SourceKind: "PLATFORM", PlatformName: "G7s", FirstKM: 90, LatestKM: 115, Count: 100, FirstEventTime: prev.Add(-24 * time.Hour), LatestEventTime: end.Add(-time.Hour), QualityStatus: "OK", QualityReason: "historical_source_baseline"}
|
|
read := func() []any {
|
|
var first, last, daily float64
|
|
var count int
|
|
var firstTS, lastTS time.Time
|
|
if err := db.QueryRow(`SELECT first_total_mileage_km,latest_total_mileage_km,daily_mileage_km,sample_count,first_event_time,latest_event_time FROM vehicle_daily_mileage_source WHERE vin=? AND source_key=?`, vin, key).Scan(&first, &last, &daily, &count, &firstTS, &lastTS); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return []any{first, last, daily, count, firstTS, lastTS}
|
|
}
|
|
var before []any
|
|
for run := 0; run < 2; run++ {
|
|
if _, err := writeAggregates(ctx, db, map[string]*metricAgg{"fixture": agg}, 500); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := read()
|
|
if got[0] != float64(100) || got[1] != float64(120) || got[2] != float64(20) || got[3] != 200 {
|
|
t.Fatalf("durable data overwritten: %+v", got)
|
|
}
|
|
if run == 1 && !reflect.DeepEqual(before, got) {
|
|
t.Fatalf("repeated backfill changed business values: before=%+v after=%+v", before, got)
|
|
}
|
|
before = got
|
|
}
|
|
var sources int
|
|
if err := db.QueryRow("SELECT COUNT(*) FROM vehicle_daily_mileage_source WHERE vin=?", vin).Scan(&sources); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sources != 3 {
|
|
t.Fatalf("missing durable alternate source: %d", sources)
|
|
}
|
|
// Newer event with a lower counter wins by time, not MAX(odometer).
|
|
agg.FirstKM, agg.FirstEventTime = 100, prev
|
|
agg.LatestKM, agg.LatestEventTime = 119, end.Add(time.Second)
|
|
if _, err := writeAggregates(ctx, db, map[string]*metricAgg{"fixture": agg}, 500); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := read(); got[1] != float64(119) || got[2] != float64(19) {
|
|
t.Fatalf("newer event rejected: %+v", got)
|
|
}
|
|
agg.LatestKM, agg.LatestEventTime = 121, end.Add(2*time.Second)
|
|
if _, err := writeAggregates(ctx, db, map[string]*metricAgg{"fixture": agg}, 500); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var final float64
|
|
if err := db.QueryRow("SELECT daily_mileage_km FROM vehicle_daily_mileage WHERE vin=?", vin).Scan(&final); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if final != 88 {
|
|
t.Fatalf("manual final overwritten: %v", final)
|
|
}
|
|
t.Log("temporary-table scenarios passed: incomplete RAW, alternate source retention, repeated backfill, event ordering, manual final; production rows modified=0")
|
|
}
|