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) } } }