167 lines
5.9 KiB
Go
167 lines
5.9 KiB
Go
package identity
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
)
|
|
|
|
func TestRefreshSnapshotResolvesKnownJT808WithoutPerFrameQueries(t *testing.T) {
|
|
db, mock := newMockDB(t)
|
|
defer db.Close()
|
|
expectIdentitySnapshot(mock)
|
|
|
|
resolver := NewMySQLResolverWithOptions(db, "vehicle_identity_binding", MySQLResolverOptions{
|
|
SnapshotOnlyLookups: true,
|
|
})
|
|
result, err := resolver.RefreshSnapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("RefreshSnapshot() error = %v", err)
|
|
}
|
|
if result.BindingEntries != 3 || result.IdentifierEntries != 2 || result.RegistrationEntries != 1 || result.SourceEntries != 1 {
|
|
t.Fatalf("snapshot result = %+v", result)
|
|
}
|
|
|
|
resolved, err := resolver.Resolve(context.Background(), envelope.FrameEnvelope{
|
|
Protocol: envelope.ProtocolJT808,
|
|
Phone: "013307795425",
|
|
SourceEndpoint: "115.231.168.135:43625",
|
|
Parsed: map[string]any{},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v", err)
|
|
}
|
|
if resolved.VIN != "LNBVIN00000000001" {
|
|
t.Fatalf("vin = %q", resolved.VIN)
|
|
}
|
|
if resolved.SourceCode != "g7s" || resolved.PlatformName != "G7s" || resolved.SourceKind != "PLATFORM" {
|
|
t.Fatalf("source metadata = code:%q platform:%q kind:%q", resolved.SourceCode, resolved.PlatformName, resolved.SourceKind)
|
|
}
|
|
identityMetadata, _ := resolved.Parsed["identity"].(map[string]any)
|
|
if identityMetadata["cache_status"] != "snapshot" {
|
|
t.Fatalf("identity metadata = %#v, want snapshot cache status", identityMetadata)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSnapshotOnlyResolverMissDoesNotQueryMySQL(t *testing.T) {
|
|
db, mock := newMockDB(t)
|
|
defer db.Close()
|
|
|
|
resolver := NewMySQLResolverWithOptions(db, "vehicle_identity_binding", MySQLResolverOptions{
|
|
SnapshotOnlyLookups: true,
|
|
})
|
|
resolved, err := resolver.Resolve(context.Background(), envelope.FrameEnvelope{
|
|
Protocol: envelope.ProtocolJT808,
|
|
Phone: "013307700000",
|
|
Parsed: map[string]any{},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v", err)
|
|
}
|
|
if resolved.VIN != "" {
|
|
t.Fatalf("vin = %q, want unresolved", resolved.VIN)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("snapshot-only miss should not query mysql: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRefreshSnapshotFailureKeepsLastKnownGoodSnapshot(t *testing.T) {
|
|
db, mock := newMockDB(t)
|
|
defer db.Close()
|
|
expectIdentitySnapshot(mock)
|
|
|
|
resolver := NewMySQLResolverWithOptions(db, "vehicle_identity_binding", MySQLResolverOptions{
|
|
SnapshotOnlyLookups: true,
|
|
})
|
|
first, err := resolver.RefreshSnapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("first RefreshSnapshot() error = %v", err)
|
|
}
|
|
mock.ExpectQuery("SELECT vin, plate, phone").
|
|
WillReturnError(errors.New("mysql unavailable"))
|
|
if _, err := resolver.RefreshSnapshot(context.Background()); err == nil {
|
|
t.Fatal("second RefreshSnapshot() error = nil, want failure")
|
|
}
|
|
|
|
resolved, err := resolver.Resolve(context.Background(), envelope.FrameEnvelope{
|
|
Protocol: envelope.ProtocolJT808,
|
|
Phone: "013307795425",
|
|
Parsed: map[string]any{},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() after failed refresh error = %v", err)
|
|
}
|
|
if resolved.VIN != "LNBVIN00000000001" {
|
|
t.Fatalf("vin after failed refresh = %q", resolved.VIN)
|
|
}
|
|
stats := resolver.CacheStats()
|
|
if !stats.SnapshotReady || stats.SnapshotRefreshedAt.IsZero() || !stats.SnapshotRefreshedAt.Equal(first.RefreshedAt) {
|
|
t.Fatalf("snapshot stats after failed refresh = %+v, first = %+v", stats, first)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func expectIdentitySnapshot(mock sqlmock.Sqlmock) {
|
|
mock.ExpectQuery("SELECT vin, plate, phone").
|
|
WillReturnRows(sqlmock.NewRows([]string{"vin", "plate", "phone"}).
|
|
AddRow("LNBVIN00000000001", "粤A00001", "13307795425"))
|
|
mock.ExpectQuery("SELECT protocol, source_code, identifier_type, identifier_value").
|
|
WillReturnRows(sqlmock.NewRows([]string{"protocol", "source_code", "identifier_type", "identifier_value", "vin", "platform_name"}).
|
|
AddRow("JT808", "g7s", "JT808_PHONE", "13307795425", "LNBVIN00000000001", "G7s"))
|
|
mock.ExpectQuery("SELECT phone, vin, device_id, plate, auth_token").
|
|
WillReturnRows(sqlmock.NewRows([]string{"phone", "vin", "device_id", "plate", "auth_token"}).
|
|
AddRow("13307795425", "LNBVIN00000000001", "DEVICE-1", "粤A00001", "device-code"))
|
|
mock.ExpectQuery("SELECT protocol, source_ip, source_code, platform_name, source_kind").
|
|
WillReturnRows(sqlmock.NewRows([]string{"protocol", "source_ip", "source_code", "platform_name", "source_kind"}).
|
|
AddRow("JT808", "115.231.168.135", "g7s", "G7s", "PLATFORM"))
|
|
}
|
|
|
|
func TestSnapshotServesJT808AuthenticationTokenByNormalizedPhone(t *testing.T) {
|
|
db, mock := newMockDB(t)
|
|
defer db.Close()
|
|
expectIdentitySnapshot(mock)
|
|
|
|
resolver := NewMySQLResolverWithOptions(db, "vehicle_identity_binding", MySQLResolverOptions{
|
|
SnapshotOnlyLookups: true,
|
|
})
|
|
if _, err := resolver.RefreshSnapshot(context.Background()); err != nil {
|
|
t.Fatalf("RefreshSnapshot() error = %v", err)
|
|
}
|
|
token, ok := resolver.JT808AuthToken("0013307795425")
|
|
if !ok || token != "device-code" {
|
|
t.Fatalf("JT808AuthToken() = %q, %v", token, ok)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSnapshotResultRefreshedAtUsesCurrentTime(t *testing.T) {
|
|
db, mock := newMockDB(t)
|
|
defer db.Close()
|
|
expectIdentitySnapshot(mock)
|
|
|
|
resolver := NewMySQLResolverWithOptions(db, "vehicle_identity_binding", MySQLResolverOptions{
|
|
SnapshotOnlyLookups: true,
|
|
})
|
|
before := time.Now()
|
|
result, err := resolver.RefreshSnapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("RefreshSnapshot() error = %v", err)
|
|
}
|
|
if result.RefreshedAt.Before(before) || result.RefreshedAt.After(time.Now()) {
|
|
t.Fatalf("refreshed_at = %v, want current time", result.RefreshedAt)
|
|
}
|
|
}
|