139 lines
5.9 KiB
Go
139 lines
5.9 KiB
Go
package dailygeo
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type resolverFunc func(context.Context, float64, float64) (Address, error)
|
|
|
|
func (f resolverFunc) Resolve(c context.Context, x, y float64) (Address, error) { return f(c, x, y) }
|
|
func TestAMapMunicipalityAndRegion(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Query().Get("key") != "test-key" {
|
|
t.Error("missing credential")
|
|
}
|
|
w.Write([]byte(`{"status":"1","regeocode":{"addressComponent":{"province":"上海市","city":[],"adcode":"310101"}}}`))
|
|
}))
|
|
defer server.Close()
|
|
a, e := (&AMap{Key: "test-key", BaseURL: server.URL}).Resolve(context.Background(), 121.47, 31.23)
|
|
if e != nil || a.Province != "上海市" || a.City != "上海市" || a.Region != "华东" {
|
|
t.Fatalf("address=%+v error=%v", a, e)
|
|
}
|
|
for p, want := range map[string]string{"广东省": "华南", "四川省": "西南", "陕西省": "西北", "山西省": "华北", "湖北省": "华中", "吉林省": "东北", "": ""} {
|
|
if Region(p) != want {
|
|
t.Fatal(p)
|
|
}
|
|
}
|
|
}
|
|
func TestAMapQuotaFailureDoesNotLeakKey(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"status":"0","infocode":"10021"}`)) }))
|
|
defer server.Close()
|
|
_, e := (&AMap{Key: "secret-value", BaseURL: server.URL}).Resolve(context.Background(), 121, 31)
|
|
if e == nil || !strings.Contains(e.Error(), "10021") || strings.Contains(e.Error(), "secret-value") {
|
|
t.Fatal(e)
|
|
}
|
|
}
|
|
func TestCachedCoordinateNeedsNoGeocoding(t *testing.T) {
|
|
db, m, e := sqlmock.New()
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
defer db.Close()
|
|
m.ExpectQuery("SELECT province,city,region,adcode").WithArgs(121.0, 31.0).WillReturnRows(sqlmock.NewRows([]string{"province", "city", "region", "adcode"}).AddRow("上海市", "上海市", "华东", "310000"))
|
|
m.ExpectExec("UPDATE vehicle_daily_geography SET province=.*WHERE vin=\\? AND stat_date=\\? AND longitude=\\? AND latitude=\\?").WithArgs("上海市", "上海市", "华东", "310000", "VIN1", "2026-08-31", 121.0, 31.0).WillReturnResult(sqlmock.NewResult(0, 1))
|
|
w := New(db, nil, "", resolverFunc(func(context.Context, float64, float64) (Address, error) {
|
|
t.Fatal("cache hit must not call external API")
|
|
return Address{}, nil
|
|
}))
|
|
if e = w.resolveOne(context.Background(), pending{"VIN1", "2026-08-31", 121, 31}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if e = m.ExpectationsWereMet(); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
}
|
|
func TestResolverFailurePersistsRetryInsteadOfFalseUnknown(t *testing.T) {
|
|
db, m, _ := sqlmock.New()
|
|
defer db.Close()
|
|
m.ExpectQuery("SELECT province,city,region,adcode").WillReturnError(sql.ErrNoRows)
|
|
m.ExpectExec("UPDATE vehicle_daily_geography SET status='ERROR'.*next_attempt_at=.*WHERE vin=\\? AND stat_date=\\? AND longitude=\\? AND latitude=\\?").WithArgs("quota", "VIN1", "2026-08-31", 121.0, 31.0).WillReturnResult(sqlmock.NewResult(0, 1))
|
|
w := New(db, nil, "", resolverFunc(func(context.Context, float64, float64) (Address, error) { return Address{}, errors.New("quota") }))
|
|
if e := w.resolveOne(context.Background(), pending{"VIN1", "2026-08-31", 121, 31}); e == nil {
|
|
t.Fatal("expected failure")
|
|
}
|
|
if e := m.ExpectationsWereMet(); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
}
|
|
func TestHistoricalFailureDoesNotMarkMissingLocation(t *testing.T) {
|
|
db, m, _ := sqlmock.New()
|
|
defer db.Close()
|
|
td, tm, _ := sqlmock.New()
|
|
defer td.Close()
|
|
tm.ExpectQuery("SELECT vin,protocol,LAST").WillReturnError(errors.New("historical store unavailable"))
|
|
w := New(db, td, "test_ts", nil)
|
|
if _, e := w.RefreshDay(context.Background(), "2026-08-31"); e == nil {
|
|
t.Fatal("expected history error")
|
|
}
|
|
if e := m.ExpectationsWereMet(); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if e := tm.ExpectationsWereMet(); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
}
|
|
func TestHistoryUsesLatestSourceInsideBusinessDate(t *testing.T) {
|
|
db, m, _ := sqlmock.New()
|
|
defer db.Close()
|
|
td, tm, _ := sqlmock.New()
|
|
defer td.Close()
|
|
early := time.Date(2026, 8, 31, 2, 0, 0, 0, Shanghai)
|
|
late := early.Add(20 * time.Hour)
|
|
tm.ExpectQuery("SELECT vin,protocol,LAST.*2026-08-31T00:00:00\\+08:00.*2026-09-01T00:00:00\\+08:00.*PARTITION BY vin,protocol").WillReturnRows(sqlmock.NewRows([]string{"vin", "protocol", "ts", "longitude", "latitude"}).AddRow("VIN1", "GB32960", late, 121.0, 31.0).AddRow("VIN1", "JT808", early, 113.0, 23.0).AddRow("VIN2", "JT808", late.Add(5*time.Hour), 110.0, 20.0))
|
|
m.ExpectExec("UPDATE vehicle_daily_geography SET longitude=NULL").WithArgs("2026-08-31").WillReturnResult(sqlmock.NewResult(0, 1))
|
|
m.ExpectExec(regexp.QuoteMeta(savePointSQL)).WithArgs("VIN1", "2026-08-31", 121.0, 31.0, late, "GB32960").WillReturnResult(sqlmock.NewResult(0, 1))
|
|
m.ExpectExec("INSERT IGNORE INTO vehicle_daily_geography").WithArgs("2026-08-31").WillReturnResult(sqlmock.NewResult(0, 0))
|
|
w := New(db, td, "test_ts", nil)
|
|
n, e := w.RefreshDay(context.Background(), "2026-08-31")
|
|
if e != nil || n != 1 {
|
|
t.Fatalf("n=%d error=%v", n, e)
|
|
}
|
|
if e = m.ExpectationsWereMet(); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if e = tm.ExpectationsWereMet(); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
}
|
|
|
|
func TestProvinceAdministeredCity(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(`{"status":"1","regeocode":{"addressComponent":{"province":"湖北省","city":[],"district":"潜江市","adcode":"429005"}}}`))
|
|
}))
|
|
defer server.Close()
|
|
a, e := (&AMap{Key: "test", BaseURL: server.URL}).Resolve(context.Background(), 112.807848, 30.381828)
|
|
if e != nil || a.City != "潜江市" || a.Region != "华中" {
|
|
t.Fatalf("address=%+v error=%v", a, e)
|
|
}
|
|
}
|
|
|
|
func TestInvalidLocationSentinel(t *testing.T) {
|
|
p := Point{VIN: "VIN1", Time: time.Now(), Longitude: -0.999999, Latitude: -0.999999}
|
|
if validPoint(p) {
|
|
t.Fatal("invalid device sentinel accepted")
|
|
}
|
|
p.Longitude, p.Latitude = 114.3, 30.5
|
|
if !validPoint(p) {
|
|
t.Fatal("valid location rejected")
|
|
}
|
|
}
|