package main import ( "context" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/stats" ) func TestChooseTrustedSourceKeepsContinuingSourceAndRejectsNewJump(t *testing.T) { previous := []dailySourceLast{{ VIN: "LA9GG64L7PBAF4001", SourceKey: normalizedSourceKey("JT808", "13307765812", "", "115.231.168.135:42630"), Phone: "13307765812", SourceEndpoint: "115.231.168.135:42630", TotalKM: 4100.8, }} current := []dailySourceLast{ { VIN: "LA9GG64L7PBAF4001", SourceKey: normalizedSourceKey("JT808", "13307765812", "", "115.159.85.149:28316"), Phone: "13307765812", SourceEndpoint: "115.159.85.149:28316", TotalKM: 42447.2, }, { VIN: "LA9GG64L7PBAF4001", SourceKey: normalizedSourceKey("JT808", "13307765812", "", "115.231.168.135:20215"), Phone: "13307765812", SourceEndpoint: "115.231.168.135:20215", TotalKM: 4123.9, }, } chosen, ok := chooseTrustedSource(current, previous) if !ok { t.Fatal("chooseTrustedSource() did not choose a source") } if chosen.current.SourceKey != normalizedSourceKey("JT808", "13307765812", "", "115.231.168.135:20215") { t.Fatalf("chosen source = %q", chosen.current.SourceKey) } if delta := chosen.current.TotalKM - chosen.previous.TotalKM; delta < 23 || delta > 24 { t.Fatalf("delta = %v, want about 23.1", delta) } } func TestDailySourceLastBuildsCandidateKeysBySourceIP(t *testing.T) { sourceA := dailySourceLast{ VIN: "LA9GG64L7PBAF4001", SourceKey: normalizedSourceKey("JT808", "13307765812", "", "115.231.168.135:20215"), Phone: "13307765812", SourceEndpoint: "115.231.168.135:20215", TotalKM: 4123.9, } sourceB := dailySourceLast{ VIN: "LA9GG64L7PBAF4001", SourceKey: normalizedSourceKey("JT808", "13307765812", "", "115.231.168.135:42630"), Phone: "13307765812", SourceEndpoint: "115.231.168.135:42630", TotalKM: 4100.8, } if sourceA.SourceKey != sourceB.SourceKey { t.Fatalf("same source IP should produce same source key: %q vs %q", sourceA.SourceKey, sourceB.SourceKey) } } func TestClearBackfillFinalMileageClearsExactKey(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock.New() error = %v", err) } defer db.Close() mock.ExpectExec(`DELETE FROM vehicle_daily_mileage WHERE vin = \? AND stat_date = \? AND protocol = \?`). WithArgs("LA9GG64L7PBAF4001", "2026-07-08", "JT808"). WillReturnResult(sqlmock.NewResult(0, 1)) if err := clearBackfillFinalMileage(context.Background(), db, "LA9GG64L7PBAF4001", "2026-07-08", envelope.ProtocolJT808); err != nil { t.Fatalf("clearBackfillFinalMileage() error = %v", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("sql expectations: %v", err) } } func TestWriteAggregatesClearsFinalBeforeProjection(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock.New() error = %v", err) } defer db.Close() mock.MatchExpectationsInOrder(true) projNow := time.Date(2026, 7, 8, 12, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)) aggregates := map[string]*metricAgg{ "LA9GG64L7PBAF4001|2026-07-08|JT808|JT808:13307765812@115.231.168.135": { VIN: "LA9GG64L7PBAF4001", Date: "2026-07-08", Protocol: envelope.ProtocolJT808, FirstKM: 4100, LatestKM: 4101, Count: 1, SourceKey: "JT808:13307765812@115.231.168.135", Phone: "13307765812", SourceEndpoint: "115.231.168.135:20215", FirstEventTime: projNow, LatestEventTime: projNow, QualityStatus: stats.QualityInvalidDelta, QualityReason: "outside_daily_range", }, } mock.ExpectExec(`INSERT INTO vehicle_data_source`). WithArgs("JT808", "115.231.168.135", "115.231.168.135:20215", sqlmock.AnyArg(), sqlmock.AnyArg()). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`INSERT INTO vehicle_daily_mileage_source`). WithArgs( "LA9GG64L7PBAF4001", "2026-07-08", "JT808", "JT808:13307765812@115.231.168.135", "115.231.168.135", "115.231.168.135:20215", "13307765812", "", "", float64(4100), float64(4101), float64(1), int64(1), projNow, projNow, stats.QualityInvalidDelta, "outside_daily_range", ). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`DELETE FROM vehicle_daily_mileage WHERE vin = \? AND stat_date = \? AND protocol = \?`). WithArgs("LA9GG64L7PBAF4001", "2026-07-08", "JT808"). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`UPDATE vehicle_daily_mileage_source`). WithArgs("LA9GG64L7PBAF4001", "2026-07-08", "JT808"). WillReturnResult(sqlmock.NewResult(0, 0)) mock.ExpectExec(`INSERT INTO vehicle_daily_mileage`). WithArgs("LA9GG64L7PBAF4001", "2026-07-08", "JT808", int64(1000)). WillReturnResult(sqlmock.NewResult(1, 0)) mock.ExpectExec(`UPDATE vehicle_daily_mileage_source s`). WithArgs( "LA9GG64L7PBAF4001", "2026-07-08", "JT808", int64(1000), "LA9GG64L7PBAF4001", "2026-07-08", "JT808", ). WillReturnResult(sqlmock.NewResult(0, 1)) written, err := writeAggregates(context.Background(), db, aggregates, 500) if err != nil { t.Fatalf("writeAggregates() error = %v", err) } if written != 1 { t.Fatalf("written = %d, want 1", written) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("sql expectations: %v", err) } }