Files
lingniu-vehicle-ingest/go/vehicle-gateway/internal/stats/source_mileage_test.go
2026-07-08 15:41:59 +08:00

160 lines
5.4 KiB
Go

package stats
import (
"context"
"strings"
"testing"
"time"
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)
func TestSourceKeyUsesProtocolDeviceAndSourceIP(t *testing.T) {
got := SourceKey(envelope.ProtocolJT808, "13307765812", "", "115.231.168.135")
if got != "JT808:13307765812@115.231.168.135" {
t.Fatalf("source key = %q", got)
}
got = SourceKey(envelope.ProtocolGB32960, "", "iccid-1", "202.98.117.132")
if got != "GB32960:iccid-1@202.98.117.132" {
t.Fatalf("source key = %q", got)
}
}
func TestUpsertSourceMileageWritesCandidateRow(t *testing.T) {
exec := &recordingExec{}
sample := SourceMileageSample{
VIN: "LA9GG64L7PBAF4001",
StatDate: "2026-07-08",
Protocol: envelope.ProtocolJT808,
SourceKey: "JT808:13307765812@115.231.168.135",
SourceIP: "115.231.168.135",
SourceEndpoint: "115.231.168.135:20215",
Phone: "13307765812",
FirstTotalKM: 4100.8,
LatestTotalKM: 4123.9,
DailyKM: 23.1,
SampleCount: 2,
FirstEventTime: time.Date(2026, 7, 8, 0, 1, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
LatestEventTime: time.Date(2026, 7, 8, 23, 59, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
QualityStatus: QualityOK,
QualityReason: "same_source_continuous",
}
if err := UpsertSourceMileage(context.Background(), exec, sample); err != nil {
t.Fatalf("UpsertSourceMileage() error = %v", err)
}
if len(exec.calls) != 1 {
t.Fatalf("exec calls = %d", len(exec.calls))
}
sql := exec.calls[0].query
for _, want := range []string{
"INSERT INTO vehicle_daily_mileage_source",
"ON DUPLICATE KEY UPDATE",
"daily_mileage_km = GREATEST(",
"quality_status = VALUES(quality_status)",
"platform_name = COALESCE(NULLIF(TRIM(VALUES(platform_name)), ''), platform_name)",
} {
if !strings.Contains(sql, want) {
t.Fatalf("candidate upsert missing %q: %s", want, sql)
}
}
if got := exec.calls[0].args[0]; got != "LA9GG64L7PBAF4001" {
t.Fatalf("first arg = %#v", got)
}
}
func TestUpsertSourceMileageSkipsBlankSourceIP(t *testing.T) {
exec := &recordingExec{}
sample := SourceMileageSample{
VIN: "LA9GG64L7PBAF4001",
StatDate: "2026-07-08",
Protocol: envelope.ProtocolJT808,
SourceKey: "JT808:13307765812@",
SourceIP: " ",
Phone: "13307765812",
QualityStatus: QualityOK,
}
if err := UpsertSourceMileage(context.Background(), exec, sample); err != nil {
t.Fatalf("UpsertSourceMileage() error = %v", err)
}
if len(exec.calls) != 0 {
t.Fatalf("exec calls = %d, want 0", len(exec.calls))
}
}
func TestProjectDailyMileageSelectsCandidateAndMarksSource(t *testing.T) {
exec := &recordingExec{}
err := ProjectDailyMileage(context.Background(), exec, "LA9GG64L7PBAF4001", "2026-07-08", envelope.ProtocolJT808)
if err != nil {
t.Fatalf("ProjectDailyMileage() error = %v", err)
}
if len(exec.calls) != 4 {
t.Fatalf("exec calls = %d, want 4", len(exec.calls))
}
updateSources := exec.calls[0].query
projectFinal := exec.calls[1].query
markSelected := exec.calls[2].query
cleanupFinal := exec.calls[3].query
if !strings.Contains(updateSources, "UPDATE vehicle_daily_mileage_source") || !strings.Contains(updateSources, "is_selected = 0") {
t.Fatalf("first query should clear selected candidates: %s", updateSources)
}
for _, want := range []string{
"INSERT INTO vehicle_daily_mileage",
"FROM vehicle_daily_mileage_source s",
"LEFT JOIN vehicle_data_source ds",
"ORDER BY COALESCE(ds.trust_priority, 100)",
"s.quality_status = '" + QualityOK + "'",
"s.daily_mileage_km BETWEEN 0 AND",
"ds.latest_source_endpoint",
} {
if !strings.Contains(projectFinal, want) {
t.Fatalf("project query missing %q: %s", want, projectFinal)
}
}
if strings.Contains(projectFinal, "COALESCE(NULLIF(ds.latest_source_endpoint, ''), s.source_endpoint)") {
t.Fatalf("project query should not fallback to source endpoint: %s", projectFinal)
}
if strings.Contains(projectFinal, ", s.source_endpoint") {
t.Fatalf("project query should only project latest_source_endpoint: %s", projectFinal)
}
if !strings.Contains(markSelected, "UPDATE vehicle_daily_mileage_source s") || !strings.Contains(markSelected, "SET s.is_selected = 1") {
t.Fatalf("mark query should flag the elected source: %s", markSelected)
}
if strings.Contains(markSelected, "JOIN vehicle_daily_mileage m") {
t.Fatalf("mark query should not rejoin final mileage for candidate selection: %s", markSelected)
}
for _, want := range []string{
"JOIN (",
"SELECT",
"s2.source_key",
"s2.quality_status = '" + QualityOK + "'",
"s2.daily_mileage_km BETWEEN 0 AND",
"COALESCE(ds.enabled, 1) = 1",
"ORDER BY COALESCE(ds.trust_priority, 100)",
"LIMIT 1",
} {
if !strings.Contains(markSelected, want) {
t.Fatalf("mark query missing %q: %s", want, markSelected)
}
}
if len(exec.calls[2].args) != 7 {
t.Fatalf("mark query args = %d, want 7", len(exec.calls[2].args))
}
if got := exec.calls[2].args[3]; got != maxSelectedDailyMileageKM {
t.Fatalf("mark query max mileage arg = %#v, want %d", got, maxSelectedDailyMileageKM)
}
for _, want := range []string{
"DELETE FROM vehicle_daily_mileage",
"NOT EXISTS (",
"FROM vehicle_daily_mileage_source",
"is_selected = 1",
} {
if !strings.Contains(cleanupFinal, want) {
t.Fatalf("cleanup query missing %q: %s", want, cleanupFinal)
}
}
if len(exec.calls[3].args) != 6 {
t.Fatalf("cleanup query args = %d, want 6", len(exec.calls[3].args))
}
}