package stats import ( "context" "math" "regexp" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope" ) func pressureEnvelope(at time.Time) envelope.FrameEnvelope { return envelope.FrameEnvelope{ Protocol: envelope.ProtocolGB32960, VIN: "LA9GG64L0NBAF4175", EventID: "event-1", EventTimeMS: at.UnixMilli(), SourceEndpoint: "10.0.0.1:9000", Fields: map[string]any{ "gb32960.fuel_cell.max_hydrogen_pressure_mpa": 6.0, "gb32960.fuel_cell.max_hydrogen_temperature_c": 44.0, "gb32960.gd_fc_vehicle_info.hydrogen_mass_kg": 99.9, }, } } func TestHydrogenStreamSampleUsesPressureTemperatureAndCapacity(t *testing.T) { loc := time.FixedZone("Asia/Shanghai", 8*3600) eventTime := time.Date(2026, 7, 21, 10, 30, 0, 0, loc) sample, reason, ok := HydrogenStreamSampleFromEnvelope(pressureEnvelope(eventTime), loc, eventTime.Add(time.Minute), 2050) if !ok || reason != "" { t.Fatalf("sample rejected: reason=%q", reason) } if sample.Date != "2026-07-21" || sample.EventID != "event-1" || sample.TankCapacityLiters != 2050 { t.Fatalf("unexpected sample: %#v", sample) } if math.Abs(sample.MassKG-9.092) > 0.01 { t.Fatalf("pressure mass = %.3fkg, want about 9.092kg", sample.MassKG) } if sample.MassKG == 99.9 { t.Fatal("reported Guangdong hydrogen mass must be ignored") } _, reason, ok = HydrogenStreamSampleFromEnvelope(pressureEnvelope(eventTime), loc, eventTime.AddDate(0, 0, 1), 2050) if ok || reason != "not_current_date" { t.Fatalf("historical sample accepted: ok=%v reason=%q", ok, reason) } } func TestHydrogenStreamRejectsMissingCapacity(t *testing.T) { loc := time.FixedZone("Asia/Shanghai", 8*3600) eventTime := time.Date(2026, 7, 21, 10, 30, 0, 0, loc) _, reason, ok := HydrogenStreamSampleFromEnvelope(pressureEnvelope(eventTime), loc, eventTime, 0) if ok || reason != "missing_tank_capacity" { t.Fatalf("missing capacity accepted: ok=%v reason=%q", ok, reason) } } func TestNISTHydrogenDensityValidationPoint(t *testing.T) { density, ok := HydrogenDensityKGPerM3(10, 26.85) if !ok || math.Abs(density-7.625) > 0.01 { t.Fatalf("density=%.6f ok=%v", density, ok) } } func TestAppendHydrogenStreamPersistsPressureEvidenceAtomically(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } defer db.Close() loc := time.FixedZone("Asia/Shanghai", 8*3600) eventTime := time.Date(2026, 7, 21, 10, 30, 0, 0, loc) mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta("INSERT INTO vehicle_open_hydrogen_stream_state")). WithArgs("LA9GG64L0NBAF4175", "2026-07-21", "10.0.0.1:9000", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), 2050.0, 6.0, 6.0, 44.0, 44.0, eventTime, eventTime, "event-1", sqlmock.AnyArg(), 20.0, sqlmock.AnyArg(), 20.0, 20.0, 20.0, sqlmock.AnyArg(), sqlmock.AnyArg(), 20.0). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(regexp.QuoteMeta("INSERT INTO vehicle_open_daily_energy")). WithArgs("LA9GG64L0NBAF4175", "2026-07-21"). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() result, err := AppendHydrogenStream(context.Background(), db, pressureEnvelope(eventTime), loc, eventTime, 2050) if err != nil { t.Fatal(err) } if result.Found != 1 || result.Written != 1 { t.Fatalf("unexpected result: %#v", result) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestWriterAppendWithResultUsesInMemoryCapacity(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } defer db.Close() loc := time.FixedZone("Asia/Shanghai", 8*3600) eventTime := time.Now().In(loc).Truncate(time.Millisecond) mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta("INSERT INTO vehicle_open_hydrogen_stream_state")).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(regexp.QuoteMeta("INSERT INTO vehicle_open_daily_energy")).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() writer := NewWriter(db, loc) writer.hydrogenTankCapacities["LA9GG64L0NBAF4175"] = 2050 result, err := writer.AppendWithResult(context.Background(), pressureEnvelope(eventTime)) if err != nil { t.Fatalf("AppendWithResult() error = %v", err) } if result.HydrogenSamplesFound != 1 || result.HydrogenSamplesWritten != 1 { t.Fatalf("hydrogen counters lost: %+v", result) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestHydrogenStreamSQLGuardsDuplicatesAndRecordsPressure(t *testing.T) { for _, want := range []string{ "VALUES(last_event_time)>last_event_time", "cycle_min_mass_kg-VALUES(last_mass_kg)>?", "tank_capacity_l", "last_pressure_mpa", "last_temperature_c", "PRESSURE_NIST", } { if !regexp.MustCompile(regexp.QuoteMeta(want)).MatchString(upsertHydrogenStreamStateSQL) { t.Fatalf("stream upsert missing %q", want) } } }