403 lines
17 KiB
Go
403 lines
17 KiB
Go
package openplatform
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
)
|
|
|
|
func TestBuildHydrogenDailyStatsUsesDirectValidBoundaries(t *testing.T) {
|
|
base := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
|
|
values := hydrogenActiveTestSegment("LTEST32960VIN0001", "source-a", base, 10.5, 9.5)
|
|
stats := BuildHydrogenDailyStats(values, "2026-07-01", 0.05, 20)
|
|
if len(stats) != 1 {
|
|
t.Fatalf("stats = %#v", stats)
|
|
}
|
|
stat := stats[0]
|
|
if stat.ConsumptionKg != 1 || stat.FirstMassKg != 10.5 || stat.LastMassKg != 9.5 {
|
|
t.Fatalf("stat = %#v", stat)
|
|
}
|
|
}
|
|
|
|
func TestBuildHydrogenDailyStatsMergesSequentialSources(t *testing.T) {
|
|
base := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
|
|
values := hydrogenActiveTestSegment("LTEST32960VIN0001", "source-a", base, 10, 9.6)
|
|
for index := len(values) / 2; index < len(values); index++ {
|
|
values[index].Source = "source-b"
|
|
}
|
|
stats := BuildHydrogenDailyStats(values, "2026-07-01", 0.05, 5)
|
|
if len(stats) != 1 {
|
|
t.Fatalf("stats = %#v", stats)
|
|
}
|
|
if stats[0].Source != "source-a" || stats[0].ConsumptionKg != 0.4 || stats[0].SampleCount != 10 {
|
|
t.Fatalf("stat = %#v", stats[0])
|
|
}
|
|
}
|
|
|
|
func TestBuildHydrogenDailyStatsDeduplicatesSameEventAcrossSources(t *testing.T) {
|
|
base := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
|
|
values := hydrogenActiveTestSegment("LTEST32960VIN0001", "source-a", base, 10, 9.6)
|
|
duplicates := append([]HydrogenObservation(nil), values...)
|
|
for index := range duplicates {
|
|
duplicates[index].Source = "source-b"
|
|
}
|
|
stats := BuildHydrogenDailyStats(append(values, duplicates...), "2026-07-01", 0.05, 5)
|
|
if len(stats) != 1 || stats[0].Source != "source-a" || stats[0].ConsumptionKg != 0.4 || stats[0].SampleCount != 10 {
|
|
t.Fatalf("stats = %#v", stats)
|
|
}
|
|
}
|
|
|
|
func TestBuildHydrogenDailyStatsOrderedMatchesUnorderedCalculation(t *testing.T) {
|
|
base := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
|
|
values := hydrogenActiveTestSegment("LTEST32960VIN0001", "source-a", base, 10, 9.6)
|
|
duplicates := append([]HydrogenObservation(nil), values...)
|
|
for index := range duplicates {
|
|
duplicates[index].Source = "source-b"
|
|
}
|
|
ordered := make([]HydrogenObservation, 0, len(values)+len(duplicates))
|
|
for index := range values {
|
|
ordered = append(ordered, values[index], duplicates[index])
|
|
}
|
|
stats := BuildHydrogenDailyStatsOrdered(ordered, "2026-07-01", 0.05, 5)
|
|
if len(stats) != 1 || stats[0].ConsumptionKg != 0.4 || stats[0].SampleCount != 10 {
|
|
t.Fatalf("stats = %#v", stats)
|
|
}
|
|
}
|
|
|
|
func TestBuildHydrogenDailyStatsDoesNotReplaceBoundariesWithWholeDayMinimum(t *testing.T) {
|
|
base := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
|
|
values := hydrogenActiveTestSegment("LTEST32960VIN0001", "source-a", base, 10, 9.8)
|
|
values = append(values[:5], append([]HydrogenObservation{{
|
|
VIN: "LTEST32960VIN0001", Source: "source-a", ObservedAt: base.Add(5 * time.Second),
|
|
MassKg: 9, NoiseKg: 0.05, RefuelThresholdKg: 1, FuelCellActive: true, FuelCellStateKnown: true,
|
|
SOCPercent: 50, SOCKnown: true, MileageKm: 10, MileageKnown: true,
|
|
VehicleState: 1, VehicleStateKnown: true, ChargeState: 3, ChargeStateKnown: true,
|
|
RunningMode: 2, RunningModeKnown: true,
|
|
}}, values[5:]...)...)
|
|
for index := 6; index < len(values); index++ {
|
|
values[index].ObservedAt = base.Add(time.Duration(index) * time.Second)
|
|
}
|
|
stats := BuildHydrogenDailyStats(values, "2026-07-01", 0.05, 5)
|
|
if len(stats) != 1 || stats[0].ConsumptionKg != 0.2 {
|
|
t.Fatalf("stats = %#v", stats)
|
|
}
|
|
}
|
|
|
|
func TestHydrogenSegmentAccumulatorKeepsOnlyBoundedEndpointWindows(t *testing.T) {
|
|
base := time.Now()
|
|
accumulator := newHydrogenSegmentAccumulator(0.05, 20)
|
|
for index := 0; index < 10000; index++ {
|
|
accumulator.Add(HydrogenObservation{
|
|
ObservedAt: base.Add(time.Duration(index) * time.Second), MassKg: 20 - float64(index)/10000,
|
|
NoiseKg: 0.05, RefuelThresholdKg: 1, FuelCellActive: true, FuelCellStateKnown: true,
|
|
})
|
|
}
|
|
if len(accumulator.segmentFirst) != hydrogenSegmentEndpointWindow || len(accumulator.segmentTail) != hydrogenSegmentEndpointWindow {
|
|
t.Fatalf("first=%d tail=%d", len(accumulator.segmentFirst), len(accumulator.segmentTail))
|
|
}
|
|
consumption, segments, _, _ := accumulator.Finalize()
|
|
if consumption <= 0 || segments != 1 {
|
|
t.Fatalf("consumption=%v segments=%d", consumption, segments)
|
|
}
|
|
}
|
|
|
|
func TestBuildHydrogenDailyStatsRejectsInactivePressureFall(t *testing.T) {
|
|
base := time.Date(2026, 8, 15, 0, 0, 0, 0, time.UTC)
|
|
stats := BuildHydrogenDailyStats([]HydrogenObservation{
|
|
{VIN: "LTEST32960VIN0001", ObservedAt: base, MassKg: 10, PressureMPa: 28.2, VehicleState: 2, VehicleStateKnown: true},
|
|
{VIN: "LTEST32960VIN0001", ObservedAt: base.Add(time.Minute), MassKg: 8, PressureMPa: 22, VehicleState: 2, VehicleStateKnown: true},
|
|
{VIN: "LTEST32960VIN0001", ObservedAt: base.Add(2 * time.Minute), MassKg: 5, PressureMPa: 12.8, VehicleState: 2, VehicleStateKnown: true},
|
|
}, "2026-08-15", 0.05, 20)
|
|
if len(stats) != 1 || stats[0].ConsumptionKg != 0 || stats[0].QualityStatus != "NO_DATA" {
|
|
t.Fatalf("stats = %#v", stats)
|
|
}
|
|
}
|
|
|
|
func hydrogenActiveTestSegment(vin, source string, start time.Time, startMass, endMass float64) []HydrogenObservation {
|
|
values := make([]HydrogenObservation, 0, hydrogenSegmentEndpointWindow*2)
|
|
for index := 0; index < hydrogenSegmentEndpointWindow*2; index++ {
|
|
mass := startMass
|
|
if index >= hydrogenSegmentEndpointWindow {
|
|
mass = endMass
|
|
}
|
|
values = append(values, HydrogenObservation{
|
|
VIN: vin, Source: source, ObservedAt: start.Add(time.Duration(index) * time.Second),
|
|
MassKg: mass, PressureMPa: mass, NoiseKg: 0.05, RefuelThresholdKg: 1,
|
|
FuelCellActive: true, FuelCellStateKnown: true,
|
|
SOCPercent: 50, SOCKnown: true,
|
|
MileageKm: float64(index) * 2, MileageKnown: true,
|
|
VehicleState: 1, VehicleStateKnown: true,
|
|
ChargeState: 3, ChargeStateKnown: true,
|
|
RunningMode: 2, RunningModeKnown: true,
|
|
})
|
|
}
|
|
return values
|
|
}
|
|
|
|
func TestExtractHydrogenMassSupportsCanonicalStringAndNumber(t *testing.T) {
|
|
for _, encoded := range []string{
|
|
`{"gb32960.gd_fc_vehicle_info.hydrogen_mass_kg":"12.3"}`,
|
|
`{"gb32960.gd_fc_vehicle_info.hydrogen_mass_kg":12.3}`,
|
|
} {
|
|
value, ok := ExtractHydrogenMass(encoded)
|
|
if !ok || value != 12.3 {
|
|
t.Fatalf("value=%v ok=%v for %s", value, ok, encoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPressureHydrogenMassMatchesGuangdongVehicle(t *testing.T) {
|
|
mass, ok := PressureHydrogenMassKg(16.4, 34, 380)
|
|
if !ok || mass < 4.47 || mass > 4.49 {
|
|
t.Fatalf("mass=%.3f ok=%v", mass, ok)
|
|
}
|
|
pressure, temperature, ok := ExtractHydrogenPressureTemperature(`{
|
|
"gb32960.fuel_cell.max_hydrogen_pressure_mpa":"16.4",
|
|
"gb32960.fuel_cell.max_hydrogen_temperature_c":34,
|
|
"gb32960.gd_fc_vehicle_info.hydrogen_mass_kg":99.9
|
|
}`)
|
|
if !ok || pressure != 16.4 || temperature != 34 {
|
|
t.Fatalf("pressure=%v temperature=%v ok=%v", pressure, temperature, ok)
|
|
}
|
|
}
|
|
|
|
func TestExtractHydrogenPressureTemperatureRejectsPlaceholders(t *testing.T) {
|
|
for _, encoded := range []string{
|
|
`{"gb32960.fuel_cell.max_hydrogen_pressure_mpa":0,"gb32960.fuel_cell.max_hydrogen_temperature_c":30}`,
|
|
`{"gb32960.fuel_cell.max_hydrogen_pressure_mpa":22.8,"gb32960.fuel_cell.max_hydrogen_temperature_c":-40}`,
|
|
} {
|
|
if _, _, ok := ExtractHydrogenPressureTemperature(encoded); ok {
|
|
t.Fatalf("pressure/temperature placeholder must be rejected: %s", encoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractHydrogenFuelCellState(t *testing.T) {
|
|
for _, test := range []struct {
|
|
encoded string
|
|
active bool
|
|
known bool
|
|
}{
|
|
{`{"gb32960.gd_fc_stack.engine_work_state":2}`, true, true},
|
|
{`{"gb32960.gd_fc_stack.engine_work_state":0}`, false, true},
|
|
{`{"gb32960.gd_fc_stack.engine_work_state":1}`, false, false},
|
|
{`{"gb32960.gd_fc_stack.engine_work_state":1,"gb32960.fuel_cell.fuel_cell_current_a":94.1}`, true, true},
|
|
{`{"gb32960.gd_fc_stack.engine_work_state":2,"gb32960.fuel_cell.fuel_cell_current_a":0}`, false, true},
|
|
{`{"gb32960.fuel_cell.fuel_cell_current_a":0.2}`, false, true},
|
|
{`{}`, false, false},
|
|
} {
|
|
active, known := ExtractHydrogenFuelCellState(test.encoded)
|
|
if active != test.active || known != test.known {
|
|
t.Fatalf("encoded=%s active=%v known=%v", test.encoded, active, known)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractHydrogenTelemetryReadsPressureTemperatureAndStateTogether(t *testing.T) {
|
|
pressure, temperature, active, known, ok := ExtractHydrogenTelemetry(`{
|
|
"gb32960.fuel_cell.max_hydrogen_pressure_mpa":28.2,
|
|
"gb32960.fuel_cell.max_hydrogen_temperature_c":35,
|
|
"gb32960.gd_fc_stack.engine_work_state":2
|
|
}`)
|
|
if !ok || pressure != 28.2 || temperature != 35 || !active || !known {
|
|
t.Fatalf("pressure=%v temperature=%v active=%v known=%v ok=%v", pressure, temperature, active, known, ok)
|
|
}
|
|
}
|
|
|
|
func TestExtractHydrogenTelemetryFastMatchesJSONDecoder(t *testing.T) {
|
|
for _, encoded := range []string{
|
|
`{"gb32960.fuel_cell.max_hydrogen_pressure_mpa":28.2,"gb32960.fuel_cell.max_hydrogen_temperature_c":35,"gb32960.gd_fc_stack.engine_work_state":2}`,
|
|
`{ "gb32960.fuel_cell.fuel_cell_current_a" : "0.5", "gb32960.fuel_cell.max_hydrogen_temperature_c" : "35", "gb32960.fuel_cell.max_hydrogen_pressure_mpa" : "28.2" }`,
|
|
`{"unrelated":"gb32960.fuel_cell.max_hydrogen_pressure_mpa","gb32960.fuel_cell.max_hydrogen_pressure_mpa":28.2,"gb32960.fuel_cell.max_hydrogen_temperature_c":35}`,
|
|
} {
|
|
wantPressure, wantTemperature, wantActive, wantKnown, wantOK := ExtractHydrogenTelemetry(encoded)
|
|
pressure, temperature, active, known, ok := extractHydrogenTelemetryFast(encoded)
|
|
if pressure != wantPressure || temperature != wantTemperature || active != wantActive || known != wantKnown || ok != wantOK {
|
|
t.Fatalf("fast=(%v,%v,%v,%v,%v) decoded=(%v,%v,%v,%v,%v) input=%s",
|
|
pressure, temperature, active, known, ok,
|
|
wantPressure, wantTemperature, wantActive, wantKnown, wantOK, encoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHydrogenObservationQueryUsesEventTimeAndScopedVIN(t *testing.T) {
|
|
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
|
start := time.Date(2026, 8, 12, 0, 0, 0, 0, loc)
|
|
query, err := buildHydrogenObservationQuery("lingniu_vehicle_ts", start, start.AddDate(0, 0, 1), "LB9A32A24R0LS1376")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []string{
|
|
"CAST(event_time AS BIGINT)",
|
|
"ts>='2026-08-12T00:00:00+08:00'",
|
|
"ts<'2026-08-13T00:00:00+08:00'",
|
|
"event_time>='2026-08-12T00:00:00+08:00'",
|
|
"event_time<'2026-08-13T00:00:00+08:00'",
|
|
"vin='LB9A32A24R0LS1376'",
|
|
"protocol='GB32960'",
|
|
"ORDER BY vin,event_time,source_endpoint,ts",
|
|
} {
|
|
if !strings.Contains(query, want) {
|
|
t.Fatalf("query missing %q:\n%s", want, query)
|
|
}
|
|
}
|
|
if strings.Contains(query, "CAST(ts AS BIGINT)") {
|
|
t.Fatalf("received time must not become observation time:\n%s", query)
|
|
}
|
|
if strings.Contains(query, "YUTONG_MQTT") || strings.Contains(query, "JT808") {
|
|
t.Fatalf("hydrogen query must only use GB32960 frames:\n%s", query)
|
|
}
|
|
if strings.Contains(query, "2026-08-05") || strings.Contains(query, "2026-08-20") {
|
|
t.Fatalf("received-time window must not include delayed frames:\n%s", query)
|
|
}
|
|
}
|
|
|
|
func TestHydrogenObservationVINQueryUsesSameDayGB32960Watermark(t *testing.T) {
|
|
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
|
start := time.Date(2026, 8, 12, 0, 0, 0, 0, loc)
|
|
query, err := buildHydrogenObservationVINQuery("lingniu_vehicle_ts", start, start.AddDate(0, 0, 1))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []string{
|
|
"SELECT DISTINCT vin", "protocol='GB32960'",
|
|
"ts>='2026-08-12T00:00:00+08:00'", "ts<'2026-08-13T00:00:00+08:00'",
|
|
"event_time>='2026-08-12T00:00:00+08:00'", "event_time<'2026-08-13T00:00:00+08:00'",
|
|
"ORDER BY vin",
|
|
} {
|
|
if !strings.Contains(query, want) {
|
|
t.Fatalf("query missing %q:\n%s", want, query)
|
|
}
|
|
}
|
|
if strings.Contains(query, "YUTONG_MQTT") || strings.Contains(query, "JT808") {
|
|
t.Fatalf("VIN query must only use GB32960:\n%s", query)
|
|
}
|
|
}
|
|
|
|
func TestLoadHydrogenObservationVINsFiltersMissingCapacity(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("SELECT DISTINCT vin").WillReturnRows(sqlmock.NewRows([]string{"vin"}).
|
|
AddRow("lb9a32a25r0ls1452").
|
|
AddRow("LB9A32A24R0LS1376"))
|
|
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
|
start := time.Date(2026, 8, 12, 0, 0, 0, 0, loc)
|
|
vins, err := LoadHydrogenObservationVINs(context.Background(), db, "lingniu_vehicle_ts", start, start.AddDate(0, 0, 1), map[string]float64{
|
|
"LB9A32A25R0LS1452": 520,
|
|
})
|
|
if err != nil || len(vins) != 1 || vins[0] != "LB9A32A25R0LS1452" {
|
|
t.Fatalf("vins=%v err=%v", vins, err)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHydrogenObservationDateRangeForAllUsesBoundedQueries(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
first := time.Date(2026, 6, 21, 8, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600))
|
|
last := time.Date(2026, 8, 18, 9, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600))
|
|
mock.ExpectQuery("ORDER BY event_time ASC LIMIT 1").
|
|
WillReturnRows(sqlmock.NewRows([]string{"event_time"}).AddRow(first))
|
|
mock.ExpectQuery("ORDER BY event_time DESC LIMIT 1").
|
|
WillReturnRows(sqlmock.NewRows([]string{"event_time"}).AddRow(last))
|
|
gotFirst, gotLast, found, err := HydrogenObservationDateRangeForAll(context.Background(), db, "lingniu_vehicle_ts")
|
|
if err != nil || !found || !gotFirst.Equal(first) || !gotLast.Equal(last) {
|
|
t.Fatalf("range first=%v last=%v found=%v err=%v", gotFirst, gotLast, found, err)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestReplaceHydrogenDailyStatsForVINDeletesOnlyScopedVehicle(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
vin := "LB9A32A24R0LS1376"
|
|
mock.ExpectBegin()
|
|
mock.ExpectExec(regexp.QuoteMeta("DELETE FROM vehicle_open_daily_energy WHERE stat_date=? AND energy_type='HYDROGEN' AND vin=?")).
|
|
WithArgs("2026-08-12", vin).
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
mock.ExpectExec(regexp.QuoteMeta("INSERT INTO vehicle_open_daily_energy(")).
|
|
WithArgs(vin, "2026-08-12", "source-a", 8.691, 7.64, 10.515, 2803, 2, "OK", "",
|
|
8.691, sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), 0.0, 0.0,
|
|
sqlmock.AnyArg(), sqlmock.AnyArg(), 0, 0, 0, "", sqlmock.AnyArg(), sqlmock.AnyArg()).
|
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
|
mock.ExpectCommit()
|
|
err = ReplaceHydrogenDailyStatsForVIN(context.Background(), db, "2026-08-12", vin, []HydrogenDailyStat{{
|
|
VIN: vin, Date: "2026-08-12", Source: "source-a", ConsumptionKg: 8.691,
|
|
FirstMassKg: 7.64, LastMassKg: 10.515, SampleCount: 2803, RefuelCount: 2, QualityStatus: "OK",
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestReplaceHydrogenDailyStatsAndSeedStreamIsAtomic(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
vin := "LB9A32A24R0LS1376"
|
|
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
|
first := HydrogenObservation{VIN: vin, Source: "source-a", ObservedAt: time.Date(2026, 8, 19, 8, 0, 0, 0, loc), MassKg: 12, TankCapacityLiter: 520, PressureMPa: 30, TemperatureC: 31, FuelCellActive: true, FuelCellStateKnown: true}
|
|
last := HydrogenObservation{VIN: vin, Source: "source-b", ObservedAt: time.Date(2026, 8, 19, 11, 0, 0, 0, loc), MassKg: 9.4, TankCapacityLiter: 520, PressureMPa: 22, TemperatureC: 35, FuelCellActive: true, FuelCellStateKnown: true}
|
|
stat := HydrogenDailyStat{
|
|
VIN: vin, Date: "2026-08-19", Source: "source-a", ConsumptionKg: 2.6,
|
|
FirstMassKg: 12, LastMassKg: 9.4, CycleMinimumMassKg: 9.4,
|
|
SampleCount: 500, RefuelCount: 1, EligibleIntervalCount: 460, QualifiedSegmentCount: 2,
|
|
FirstObservation: first, LastObservation: last, QualityStatus: "OK",
|
|
}
|
|
mock.ExpectBegin()
|
|
mock.ExpectExec(regexp.QuoteMeta("DELETE FROM vehicle_open_daily_energy WHERE stat_date=? AND energy_type='HYDROGEN'")).
|
|
WithArgs("2026-08-19").WillReturnResult(sqlmock.NewResult(0, 3))
|
|
mock.ExpectExec(regexp.QuoteMeta("DELETE FROM vehicle_open_hydrogen_segment_stream_state WHERE stat_date=?")).
|
|
WithArgs("2026-08-19").WillReturnResult(sqlmock.NewResult(0, 0))
|
|
mock.ExpectExec(regexp.QuoteMeta("INSERT INTO vehicle_open_daily_energy(")).
|
|
WithArgs(vin, "2026-08-19", "source-a", 2.6, 12.0, 9.4, 500, 1, "OK", "",
|
|
2.6, sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), 0.0, 0.0,
|
|
sqlmock.AnyArg(), sqlmock.AnyArg(), 0, 2, 0, "", sqlmock.AnyArg(), sqlmock.AnyArg()).
|
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
|
mock.ExpectExec(regexp.QuoteMeta("INSERT INTO vehicle_open_hydrogen_segment_stream_state(")).
|
|
WithArgs(vin, "2026-08-19", "source-a", 2.6, 2.6, 500, 1, 0, 460, 2, 9.4, last.ObservedAt, "", sqlmock.AnyArg(), "OK", "").
|
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
|
mock.ExpectCommit()
|
|
if err := ReplaceHydrogenDailyStatsAndSeedStream(context.Background(), db, "2026-08-19", []HydrogenDailyStat{stat}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
encoded, err := hydrogenSegmentStreamSeedJSON(stat)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var state map[string]any
|
|
if err := json.Unmarshal(encoded, &state); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if state["lastObservedMassKg"] != 9.4 || state["finalizedConsumptionKg"] != 2.6 || state["lastEventTime"] == "" {
|
|
t.Fatalf("seed state=%s", encoded)
|
|
}
|
|
}
|