package main
import (
"encoding/json"
"math"
"math/rand"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"macker/datapoint"
)
const addressFixture = `
`
func TestImportLocalAddresses(t *testing.T) {
points, err := importAddresses(strings.NewReader(addressFixture))
if err != nil {
t.Fatal(err)
}
if len(points) != 2 || points[0].Label != "100 Main Street, Jackson" || points[1].Label != "200 Oak Street" {
t.Fatalf("bad addresses: %+v", points)
}
if math.Abs(points[1].Lat-32.315) > 1e-8 || math.Abs(points[1].Lon-(-90.195)) > 1e-8 {
t.Fatalf("incorrect closed-way position: %+v", points[1])
}
for _, input := range []string{""} {
if _, err = importAddresses(strings.NewReader(input)); err == nil {
t.Fatal("accepted invalid/empty import")
}
}
}
func TestLocalAddressBuildLoadAndAPI(t *testing.T) {
dir := t.TempDir()
source := filepath.Join(dir, "source.osm")
target := filepath.Join(dir, "data", "addresses.json")
if err := os.WriteFile(source, []byte(addressFixture), 0600); err != nil {
t.Fatal(err)
}
if count, err := buildAddresses(source, target); err != nil || count != 2 {
t.Fatalf("import: %d %v", count, err)
}
index, err := loadAddresses(target)
if err != nil {
t.Fatal(err)
}
tr := &tracker{addresses: index}
tr.accept([]datapoint.DataPoint{{Lat: 32.3, Long: -90.2}}, time.Now())
rr := httptest.NewRecorder()
tr.handler().ServeHTTP(rr, httptest.NewRequest("GET", "/api/location", nil))
var got location
if err = json.Unmarshal(rr.Body.Bytes(), &got); err != nil {
t.Fatal(err)
}
if got.Nearby.Status != "ready" || got.Nearby.Address.Label != "100 Main Street, Jackson" || got.Nearby.Address.DistanceMeters != 0 {
t.Fatalf("bad response: %s", rr.Body.String())
}
if index.view(30.4, -89.1).Status != "unavailable" {
t.Fatal("returned distant address outside search radius")
}
// A failed refresh must preserve the last usable database.
if err = os.WriteFile(source, []byte(""), 0600); err != nil {
t.Fatal(err)
}
if _, err = buildAddresses(source, target); err == nil {
t.Fatal("bad refresh accepted")
}
if _, err = loadAddresses(target); err != nil {
t.Fatalf("old index lost after failed refresh: %v", err)
}
}
func TestLocalAddressIndexMatchesFullScan(t *testing.T) {
random := rand.New(rand.NewSource(42))
idx := &addressIndex{cells: make(map[addressCell][]addressPoint)}
var all []addressPoint
for i := 0; i < 2000; i++ {
p := addressPoint{Lat: 30.2 + random.Float64()*2.3, Lon: -91.6 + random.Float64()*3.3, Label: "Test address"}
all = append(all, p)
c := cellFor(p.Lat, p.Lon)
idx.cells[c] = append(idx.cells[c], p)
}
for i := 0; i < 300; i++ {
lat, lon := 30.2+random.Float64()*2.3, -91.6+random.Float64()*3.3
best := math.Inf(1)
for _, p := range all {
best = math.Min(best, distanceMeters(lat, lon, p.Lat, p.Lon))
}
got := idx.view(lat, lon)
if best > localAddressRange {
if got.Status != "unavailable" {
t.Fatal("range limit ignored")
}
continue
}
if got.Address == nil || math.Abs(got.Address.DistanceMeters-best) > 0.001 {
t.Fatalf("index missed nearest address: %+v; want %f", got, best)
}
}
}