139 lines
4.7 KiB
Go
139 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|