213 lines
8.4 KiB
Go
213 lines
8.4 KiB
Go
package openplatform
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
)
|
|
|
|
func TestInQueryPreservesMySQLDateFormatPercentTokens(t *testing.T) {
|
|
query, args := inQuery("SELECT DATE_FORMAT(stat_date,'%Y-%m-%d') FROM metrics WHERE stat_date=? AND vin IN (%s)", "2026-07-21", []string{"VIN1", "VIN2"})
|
|
if strings.Contains(query, "MISSING") || !strings.Contains(query, "DATE_FORMAT(stat_date,'%Y-%m-%d')") {
|
|
t.Fatalf("date format was corrupted: %s", query)
|
|
}
|
|
if !strings.Contains(query, "vin IN (?,?)") {
|
|
t.Fatalf("VIN placeholders missing: %s", query)
|
|
}
|
|
if len(args) != 3 || args[0] != "2026-07-21" || args[1] != "VIN1" || args[2] != "VIN2" {
|
|
t.Fatalf("unexpected args: %#v", args)
|
|
}
|
|
}
|
|
|
|
func TestAuthorizedVehiclesUsesBinaryVINJoin(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
start := time.Date(2026, 7, 20, 0, 0, 0, 0, time.Local)
|
|
end := start.Add(24*time.Hour - time.Nanosecond)
|
|
mock.ExpectQuery(regexp.QuoteMeta("JOIN vehicle_identity_binding b ON BINARY b.vin=BINARY g.vin")).
|
|
WithArgs(uint64(1), start, end, "辽A00001").
|
|
WillReturnRows(sqlmock.NewRows([]string{"plate", "vin"}).AddRow("辽A00001", "LTEST000000000001"))
|
|
|
|
repository := NewMySQLRepository(db)
|
|
vehicles, err := repository.AuthorizedVehicles(context.Background(), 1, []string{"辽A00001"}, start, end)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if vehicles["辽A00001"].VIN != "LTEST000000000001" {
|
|
t.Fatalf("unexpected vehicles: %#v", vehicles)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAuthorizedVehiclesWithoutPlateFilterReturnsAllGrantedVehicles(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
start := time.Date(2026, 7, 20, 0, 0, 0, 0, time.Local)
|
|
end := start.Add(24 * time.Hour)
|
|
mock.ExpectQuery("JOIN vehicle_identity_binding.*TRIM.*ORDER BY UPPER").
|
|
WithArgs(uint64(7), start, end).
|
|
WillReturnRows(sqlmock.NewRows([]string{"plate", "vin"}).
|
|
AddRow("粤A12345", "LTEST32960VIN0001").
|
|
AddRow("粤B67890", "LTEST32960VIN0002"))
|
|
|
|
vehicles, err := NewMySQLRepository(db).AuthorizedVehicles(context.Background(), 7, nil, start, end)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(vehicles) != 2 || vehicles["粤A12345"].VIN != "LTEST32960VIN0001" || vehicles["粤B67890"].VIN != "LTEST32960VIN0002" {
|
|
t.Fatalf("vehicles=%#v", vehicles)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestDailyMileageReturnsDailyAndSameProtocolEndTotal(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("SELECT MAX\\(selected.latest_event_time\\).*FROM vehicle_daily_mileage m\\s+WHERE m.stat_date BETWEEN \\? AND \\?.*m.latest_total_mileage_km>=0.*m.daily_mileage_km>=0").
|
|
WithArgs("2026-07-21", "2026-07-21", "LTEST32960VIN0001", "LTEST32960VIN0002").
|
|
WillReturnRows(sqlmock.NewRows([]string{"vin", "date", "protocol", "daily_mileage_km", "latest_total_mileage_km", "data_time", "updated_at"}).
|
|
AddRow("LTEST32960VIN0001", "2026-07-21", "GB32960", 101.235, 12345.679, "2026-07-21T23:58:45+08:00", "2026-07-22T05:10:00+08:00"))
|
|
|
|
values, err := NewMySQLRepository(db).DailyMileage(context.Background(), []string{"LTEST32960VIN0001", "LTEST32960VIN0002"}, "2026-07-21", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first := values["LTEST32960VIN0001"]
|
|
if first.MileageKm != 101.235 || first.TotalMileageKm != 12345.679 || first.DataTime != "2026-07-21T23:58:45+08:00" || first.UpdatedAt == "" {
|
|
t.Fatalf("first=%#v", first)
|
|
}
|
|
if _, ok := values["LTEST32960VIN0002"]; ok {
|
|
t.Fatalf("vehicle without a cumulative total must not be returned: %#v", values)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestDailyMileageExplicitPriorityFiltersDisabledProtocolsAndKeepsZero(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("m.protocol IN \\(\\?,\\?\\).*ORDER BY m.stat_date,m.vin,CASE m.protocol WHEN \\? THEN 1 WHEN \\? THEN 2").
|
|
WithArgs("2026-07-21", "2026-07-21", "LTEST32960VIN0001", "JT808", "GB32960", "JT808", "GB32960").
|
|
WillReturnRows(sqlmock.NewRows([]string{"vin", "date", "protocol", "daily_mileage_km", "latest_total_mileage_km", "data_time", "updated_at"}).
|
|
AddRow("LTEST32960VIN0001", "2026-07-21", "JT808", 0.0, 12000.0, "2026-07-21T23:58:45+08:00", "2026-07-22T05:10:00+08:00").
|
|
AddRow("LTEST32960VIN0001", "2026-07-21", "GB32960", 12.0, 12012.0, "2026-07-21T23:59:00+08:00", "2026-07-22T05:10:00+08:00"))
|
|
|
|
values, err := NewMySQLRepository(db).DailyMileage(context.Background(), []string{"LTEST32960VIN0001"}, "2026-07-21", []string{"JT808", "GB32960"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
selected := values["LTEST32960VIN0001"]
|
|
if selected.Protocol != "JT808" || selected.MileageKm != 0 {
|
|
t.Fatalf("selected=%#v", selected)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestLatestMileageBeforeUsesRequestedProtocolPriority(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("SELECT MAX\\(selected.latest_event_time\\).*SELECT prior.vin,prior.protocol,MAX\\(prior.stat_date\\).*FROM vehicle_daily_mileage prior\\s+WHERE prior.stat_date<\\?.*prior.protocol IN \\(\\?,\\?\\).*ORDER BY m.vin,CASE m.protocol").
|
|
WithArgs("2026-07-22", "LTEST32960VIN0001", "JT808", "YUTONG_MQTT", "JT808", "YUTONG_MQTT").
|
|
WillReturnRows(sqlmock.NewRows([]string{"vin", "date", "protocol", "daily_mileage_km", "latest_total_mileage_km", "data_time", "updated_at"}).
|
|
AddRow("LTEST32960VIN0001", "2026-07-20", "JT808", 18.5, 9008.5, "2026-07-20T22:00:00+08:00", "2026-07-21T01:00:00+08:00").
|
|
AddRow("LTEST32960VIN0001", "2026-07-21", "YUTONG_MQTT", 20.0, 12020.0, "2026-07-21T23:00:00+08:00", "2026-07-22T01:00:00+08:00"))
|
|
|
|
values, err := NewMySQLRepository(db).LatestMileageBefore(context.Background(), []string{"LTEST32960VIN0001"}, "2026-07-22", []string{"JT808", "YUTONG_MQTT"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
selected := values["LTEST32960VIN0001"]
|
|
if selected.Protocol != "JT808" ||
|
|
selected.MileageKm != 18.5 ||
|
|
selected.TotalMileageKm != 9008.5 ||
|
|
selected.DataTime != "2026-07-20T22:00:00+08:00" ||
|
|
selected.UpdatedAt != "2026-07-21T01:00:00+08:00" {
|
|
t.Fatalf("selected=%#v", selected)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestTotalMileageUsesProtocolPriorityAndLatestRecordAtOrBeforeTime(t *testing.T) {
|
|
mysqlDB, _, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer mysqlDB.Close()
|
|
tdDB, tdMock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tdDB.Close()
|
|
at := time.Date(2026, 7, 21, 9, 30, 0, 0, time.FixedZone("CST", 8*3600))
|
|
tdMock.ExpectQuery("protocol='GB32960'.*ts<='2026-07-21T09:30:00\\+08:00'.*ORDER BY ts DESC LIMIT 1").
|
|
WillReturnRows(sqlmock.NewRows([]string{"ts", "total_mileage_km", "protocol"}))
|
|
tdMock.ExpectQuery("protocol='YUTONG_MQTT'.*ts<='2026-07-21T09:30:00\\+08:00'.*ORDER BY ts DESC LIMIT 1").
|
|
WillReturnRows(sqlmock.NewRows([]string{"ts", "total_mileage_km", "protocol"}).AddRow(at.Add(-15*time.Second).UnixMilli(), 12345.678, "YUTONG_MQTT"))
|
|
repository := NewMySQLRepository(mysqlDB).WithTDengine(tdDB, "lingniu_vehicle_ts")
|
|
point, err := repository.TotalMileage(context.Background(), "LA9GG68L2PBAF4790", at, []string{"GB32960", "YUTONG_MQTT", "JT808"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if point == nil || point.Protocol != "YUTONG_MQTT" || point.TotalMileageKm != 12345.678 || !point.ObservedAt.Equal(at.Add(-15*time.Second)) {
|
|
t.Fatalf("point=%#v", point)
|
|
}
|
|
if err := tdMock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestListVehicleGrantsUsesBinaryVINJoin(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.Local)
|
|
mock.ExpectQuery(regexp.QuoteMeta("LEFT JOIN vehicle_identity_binding b ON BINARY b.vin=BINARY g.vin")).
|
|
WithArgs(uint64(1)).
|
|
WillReturnRows(sqlmock.NewRows([]string{"vin", "plate", "valid_from", "valid_to", "granted_by", "updated_at"}).
|
|
AddRow("LTEST000000000001", "辽A00001", now, now.AddDate(1, 0, 0), "admin", now))
|
|
|
|
repository := NewMySQLRepository(db)
|
|
grants, err := repository.ListVehicleGrants(context.Background(), 1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(grants) != 1 || grants[0].Plate != "辽A00001" {
|
|
t.Fatalf("unexpected grants: %#v", grants)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|