feat: expand vehicle data platform capabilities
This commit is contained in:
138
go/vehicle-gateway/internal/stats/hydrogen_stream_test.go
Normal file
138
go/vehicle-gateway/internal/stats/hydrogen_stream_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user