Files
lingniu-vehicle-ingest/go/vehicle-gateway/internal/realtime/repository_test.go
2026-07-02 00:21:50 +08:00

240 lines
6.6 KiB
Go

package realtime
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/redis/go-redis/v9"
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)
func TestRepositoryUpdatesMergedAndProtocolSnapshots(t *testing.T) {
repo, closeFn := newTestRepository(t)
defer closeFn()
ctx := context.Background()
if err := repo.Update(ctx, envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
VIN: "VIN001",
EventTimeMS: 1000,
ReceivedAtMS: 1100,
Fields: map[string]any{
envelope.FieldLongitude: 121.1,
envelope.FieldTotalMileageKM: 10.5,
},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
if err := repo.Update(ctx, envelope.FrameEnvelope{
Protocol: envelope.ProtocolGB32960,
VIN: "VIN001",
EventTimeMS: 900,
ReceivedAtMS: 1200,
Fields: map[string]any{
envelope.FieldLongitude: 120.0,
envelope.FieldLatitude: 30.5,
},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
merged, err := repo.GetMerged(ctx, "VIN001")
if err != nil {
t.Fatalf("GetMerged() error = %v", err)
}
if merged.Fields[envelope.FieldLongitude] != 121.1 {
t.Fatalf("older longitude overwrote newer value: %#v", merged.Fields)
}
if merged.Fields[envelope.FieldLatitude] != 30.5 {
t.Fatalf("new latitude missing: %#v", merged.Fields)
}
protocol, err := repo.GetProtocol(ctx, "VIN001", envelope.ProtocolJT808)
if err != nil {
t.Fatalf("GetProtocol() error = %v", err)
}
if protocol.Fields[envelope.FieldTotalMileageKM] != 10.5 {
t.Fatalf("protocol snapshot = %#v", protocol)
}
}
func TestRepositoryOnlineStatus(t *testing.T) {
repo, closeFn := newTestRepository(t)
defer closeFn()
ctx := context.Background()
status, err := repo.IsOnline(ctx, "VIN001")
if err != nil {
t.Fatalf("IsOnline() error = %v", err)
}
if status.Online {
t.Fatal("empty vin should be offline")
}
if err := repo.Update(ctx, envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
VIN: "VIN001",
EventTimeMS: 1000,
ReceivedAtMS: 1100,
Fields: map[string]any{envelope.FieldSpeedKMH: 12.3},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
status, err = repo.IsOnline(ctx, "VIN001")
if err != nil {
t.Fatalf("IsOnline() error = %v", err)
}
if !status.Online || status.LastSeenMS != 1100 {
t.Fatalf("online status = %#v", status)
}
}
func TestRepositoryUpdatesPhoneOnlyVehicleKey(t *testing.T) {
repo, closeFn := newTestRepository(t)
defer closeFn()
ctx := context.Background()
if err := repo.Update(ctx, envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
Phone: "013307811170",
EventTimeMS: 1000,
ReceivedAtMS: 1100,
Fields: map[string]any{
envelope.FieldSpeedKMH: 23.0,
envelope.FieldTotalMileageKM: 10003.7,
},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
vehicleKey := "JT808:013307811170"
merged, err := repo.GetMerged(ctx, vehicleKey)
if err != nil {
t.Fatalf("GetMerged() error = %v", err)
}
if merged.VehicleKey != vehicleKey || merged.VIN != "" {
t.Fatalf("unexpected identity: %#v", merged)
}
if merged.Fields[envelope.FieldTotalMileageKM] != 10003.7 {
t.Fatalf("merged fields = %#v", merged.Fields)
}
status, err := repo.IsOnline(ctx, vehicleKey)
if err != nil {
t.Fatalf("IsOnline() error = %v", err)
}
if !status.Online || status.VehicleKey != vehicleKey {
t.Fatalf("online status = %#v", status)
}
}
func TestRepositoryDoesNotOverwritePositiveMileageWithZero(t *testing.T) {
repo, closeFn := newTestRepository(t)
defer closeFn()
ctx := context.Background()
if err := repo.Update(ctx, envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
VIN: "VIN001",
EventTimeMS: 1000,
ReceivedAtMS: 1100,
Fields: map[string]any{
envelope.FieldTotalMileageKM: 12345.6,
},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
if err := repo.Update(ctx, envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
VIN: "VIN001",
EventTimeMS: 2000,
ReceivedAtMS: 2100,
Fields: map[string]any{
envelope.FieldSpeedKMH: 22.0,
envelope.FieldTotalMileageKM: 0,
},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
merged, err := repo.GetMerged(ctx, "VIN001")
if err != nil {
t.Fatalf("GetMerged() error = %v", err)
}
if merged.Fields[envelope.FieldTotalMileageKM] != 12345.6 {
t.Fatalf("zero mileage overwrote positive value: %#v", merged.Fields)
}
if merged.Fields[envelope.FieldSpeedKMH] != 22.0 {
t.Fatalf("new speed should still merge: %#v", merged.Fields)
}
}
func TestHandlerReturnsMergedSnapshot(t *testing.T) {
repo, closeFn := newTestRepository(t)
defer closeFn()
if err := repo.Update(context.Background(), envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
VIN: "VIN001",
EventTimeMS: 1000,
ReceivedAtMS: 1100,
Fields: map[string]any{envelope.FieldSpeedKMH: 12.3},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/realtime/vehicles/VIN001", nil)
rec := httptest.NewRecorder()
NewHandler(repo).ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
if !stringsContains(rec.Body.String(), `"vin":"VIN001"`) {
t.Fatalf("unexpected body: %s", rec.Body.String())
}
}
func TestHandlerReturnsPhoneOnlyVehicleKeySnapshot(t *testing.T) {
repo, closeFn := newTestRepository(t)
defer closeFn()
if err := repo.Update(context.Background(), envelope.FrameEnvelope{
Protocol: envelope.ProtocolJT808,
Phone: "013307811170",
EventTimeMS: 1000,
ReceivedAtMS: 1100,
Fields: map[string]any{envelope.FieldSpeedKMH: 12.3},
}); err != nil {
t.Fatalf("Update() error = %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/realtime/vehicles/JT808:013307811170", nil)
rec := httptest.NewRecorder()
NewHandler(repo).ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
if !stringsContains(rec.Body.String(), `"vehicle_key":"JT808:013307811170"`) {
t.Fatalf("unexpected body: %s", rec.Body.String())
}
}
func newTestRepository(t *testing.T) (*Repository, func()) {
t.Helper()
server, err := miniredis.Run()
if err != nil {
t.Fatalf("miniredis.Run() error = %v", err)
}
client := redis.NewClient(&redis.Options{Addr: server.Addr()})
return NewRepository(client, Config{OnlineTTL: time.Minute}), func() {
_ = client.Close()
server.Close()
}
}
func stringsContains(value string, pattern string) bool {
return strings.Contains(value, pattern)
}