perf(api): make batch vehicle search exact
This commit is contained in:
@@ -911,6 +911,27 @@ func TestHandlerVehicleRealtimeAcceptsBatchKeywords(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerVehicleRealtimeBatchKeywordsAreExact(t *testing.T) {
|
||||
handler := NewHandler(NewService(NewMockStore()))
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/realtime/vehicles?keywords=%E7%B2%A4AG1831&limit=10", nil)
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var body struct {
|
||||
Data struct {
|
||||
Items []VehicleRealtimeRow `json:"items"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("response JSON should decode: %v body=%s", err, rec.Body.String())
|
||||
}
|
||||
if len(body.Data.Items) != 0 {
|
||||
t.Fatalf("copied batch plates must not fuzzy-match similar identities, got %+v body=%s", body.Data.Items, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerHistoryMileageQualityOps(t *testing.T) {
|
||||
cases := []struct {
|
||||
path string
|
||||
|
||||
@@ -486,8 +486,17 @@ func buildArchiveMissingFieldStats(missingPlate, missingPhone, missingOEM int) [
|
||||
func (m *MockStore) VehicleRealtime(_ context.Context, query url.Values) (Page[VehicleRealtimeRow], error) {
|
||||
rows := m.locations
|
||||
if keywords := vehicleSearchKeywords(query); len(keywords) > 0 {
|
||||
batch := strings.TrimSpace(query.Get("keywords")) != ""
|
||||
rows = keep(rows, func(row RealtimeLocationRow) bool {
|
||||
vehicle := m.vehicleByVIN(row.VIN)
|
||||
if batch {
|
||||
for _, keyword := range keywords {
|
||||
if strings.EqualFold(row.VIN, keyword) || strings.EqualFold(row.Plate, keyword) || strings.EqualFold(vehicle.Plate, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
haystack := strings.ToLower(row.VIN + row.Plate + vehicle.Plate + vehicle.Phone + vehicle.OEM)
|
||||
for _, keyword := range keywords {
|
||||
if strings.Contains(haystack, strings.ToLower(keyword)) {
|
||||
|
||||
@@ -329,13 +329,20 @@ func buildVehicleRealtimeSQL(query url.Values) SQLQuery {
|
||||
args = append(args, protocol)
|
||||
}
|
||||
if keywords := vehicleSearchKeywords(query); len(keywords) > 0 {
|
||||
matches := make([]string, 0, len(keywords))
|
||||
for _, keyword := range keywords {
|
||||
matches = append(matches, "(l.vin LIKE ? OR l.plate LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
|
||||
if strings.TrimSpace(query.Get("keywords")) != "" {
|
||||
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(keywords)), ",")
|
||||
where = append(where, "(l.vin IN ("+placeholders+") OR l.plate IN ("+placeholders+") OR b.plate IN ("+placeholders+"))")
|
||||
for range 3 {
|
||||
for _, keyword := range keywords {
|
||||
args = append(args, keyword)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
keyword := keywords[0]
|
||||
like := "%" + keyword + "%"
|
||||
where = append(where, "(l.vin LIKE ? OR l.plate LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
|
||||
args = append(args, like, like, like, like, like)
|
||||
}
|
||||
where = append(where, "("+strings.Join(matches, " OR ")+")")
|
||||
}
|
||||
switch strings.TrimSpace(query.Get("online")) {
|
||||
case "online":
|
||||
|
||||
@@ -3,6 +3,7 @@ package platform
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -217,17 +218,39 @@ func TestBuildVehicleRealtimeSQL(t *testing.T) {
|
||||
|
||||
func TestBuildVehicleRealtimeSQLFiltersMultipleVehicleKeywords(t *testing.T) {
|
||||
built := buildVehicleRealtimeSQL(url.Values{"keywords": {"粤AG18312, 川AHTWO1, 粤AG18312"}, "limit": {"10"}})
|
||||
if !strings.Contains(built.Text, ") OR (l.vin LIKE ?") || !strings.Contains(built.CountText, ") OR (l.vin LIKE ?") {
|
||||
t.Fatalf("batch vehicle search must match any keyword in rows and count: %s / %s", built.Text, built.CountText)
|
||||
for _, text := range []string{built.Text, built.CountText} {
|
||||
if !strings.Contains(text, "l.vin IN (?,?)") || !strings.Contains(text, "l.plate IN (?,?)") || !strings.Contains(text, "b.plate IN (?,?)") {
|
||||
t.Fatalf("batch vehicle search must use exact identity sets in rows and count: %s", text)
|
||||
}
|
||||
if strings.Contains(text, "LIKE") {
|
||||
t.Fatalf("batch vehicle search must not expand copied plates into fuzzy predicates: %s", text)
|
||||
}
|
||||
}
|
||||
if len(built.Args) != 12 || built.Args[0] != "%粤AG18312%" || built.Args[5] != "%川AHTWO1%" || built.Args[10] != 10 {
|
||||
if len(built.Args) != 8 || built.Args[0] != "粤AG18312" || built.Args[1] != "川AHTWO1" || built.Args[2] != "粤AG18312" || built.Args[6] != 10 {
|
||||
t.Fatalf("batch args = %#v", built.Args)
|
||||
}
|
||||
if len(built.CountArgs) != 10 || built.CountArgs[0] != "%粤AG18312%" || built.CountArgs[5] != "%川AHTWO1%" {
|
||||
if len(built.CountArgs) != 6 || built.CountArgs[0] != "粤AG18312" || built.CountArgs[1] != "川AHTWO1" || built.CountArgs[4] != "粤AG18312" {
|
||||
t.Fatalf("batch count args = %#v", built.CountArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVehicleRealtimeSQLBoundsCopiedPlateSet(t *testing.T) {
|
||||
keywords := make([]string, 0, vehicleSearchKeywordLimit+10)
|
||||
for index := 0; index < vehicleSearchKeywordLimit+10; index++ {
|
||||
keywords = append(keywords, fmt.Sprintf("粤A%05d", index))
|
||||
}
|
||||
built := buildVehicleRealtimeSQL(url.Values{"keywords": {strings.Join(keywords, ",")}, "limit": {"10"}})
|
||||
if len(built.Args) != vehicleSearchKeywordLimit*3+2 {
|
||||
t.Fatalf("bounded batch args = %d, want %d", len(built.Args), vehicleSearchKeywordLimit*3+2)
|
||||
}
|
||||
if len(built.CountArgs) != vehicleSearchKeywordLimit*3 {
|
||||
t.Fatalf("bounded batch count args = %d, want %d", len(built.CountArgs), vehicleSearchKeywordLimit*3)
|
||||
}
|
||||
if slices.Contains(built.Args, "粤A00100") {
|
||||
t.Fatalf("batch query must ignore identities beyond the %d item boundary", vehicleSearchKeywordLimit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVehicleRealtimeSQLFiltersServiceStatus(t *testing.T) {
|
||||
query := url.Values{"serviceStatus": {"degraded"}, "limit": {"8"}}
|
||||
built := buildVehicleRealtimeSQL(query)
|
||||
|
||||
Reference in New Issue
Block a user