The old link only ever surfaced one nearby article at a time, buried in whatever POI's popup you happened to open, and only for uploaded files with a category. Replaced it with a standalone "Wikipedia" toggle under the renamed "Tracks & Points" panel: enabling it adds a live map layer that refetches GET /api/kiwix/nearby on every pan/zoom and plots every nearby article as its own point. kiwix-serve's own geo search never echoes back a result's coordinates (confirmed live against the real production ZIM, both its HTML and ?format=xml output) — it only filters by distance from a point. So Maptool::Kiwix.nearby fetches each matching article's own page too and reads its <meta name="geo.position"> tag, which every geotagged Wikipedia article carries. Tried parallelizing those per-article fetches with threads first; measured no improvement against the real ZIM (kiwix-serve appears to serialize requests regardless), so it stayed sequential — ~1.6s for a 30-result page, fine for a refetch-on-pan-end layer. Clicking a point opens a small popup linking to the article (proxied through the existing /kiwix/* passthrough). New WIKI_ICON (NPS's bookstore-22.svg) marks these, kept out of POI_ICONS since that set doubles as notes.js's icon-picker option list and this fixed-glyph layer has no per-feature category to key off of.
128 lines
5.2 KiB
Ruby
128 lines
5.2 KiB
Ruby
require_relative "test_helper"
|
|
require "maptool/kiwix"
|
|
|
|
# No real network calls — a blank URL and a guaranteed-local connection
|
|
# refusal are both fully offline/deterministic; simulating a genuine
|
|
# reachable kiwix-serve would need spinning up a real HTTP server for what
|
|
# is, at heart, a two-line Net::HTTP wrapper — not worth it, same
|
|
# restraint LiveTrackClientTest already applies to its own network path.
|
|
class KiwixTest < Minitest::Test
|
|
def test_reachable_is_false_for_a_blank_url
|
|
refute Maptool::Kiwix.reachable?("")
|
|
refute Maptool::Kiwix.reachable?(nil)
|
|
end
|
|
|
|
def test_reachable_is_false_when_nothing_is_listening
|
|
refute Maptool::Kiwix.reachable?("http://127.0.0.1:1", open_timeout: 1)
|
|
end
|
|
|
|
# A bare TCPServer stub (not WEBrick — not a project dependency, and this
|
|
# only needs to echo back what request line it received) confirms .proxy
|
|
# actually builds the target URL from path + query_string correctly, not
|
|
# just that it can complete *a* request (already covered by nothing here
|
|
# exercising a wrong path/query, unlike .reachable? which only ever hits
|
|
# "/").
|
|
def test_proxy_forwards_path_and_query_and_returns_the_response
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
port = server.addr[1]
|
|
request_line = nil
|
|
thread = Thread.new do
|
|
client = server.accept
|
|
request_line = client.gets
|
|
client.print("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nhi")
|
|
client.close
|
|
end
|
|
|
|
response = Maptool::Kiwix.proxy("http://127.0.0.1:#{port}", "/kiwix/search", "pattern=&latitude=1")
|
|
thread.join
|
|
server.close
|
|
|
|
assert_equal "GET /kiwix/search?pattern=&latitude=1 HTTP/1.1\r\n", request_line
|
|
assert_equal "200", response.code
|
|
assert_equal "hi", response.body
|
|
end
|
|
|
|
def test_proxy_returns_nil_when_nothing_is_listening
|
|
assert_nil Maptool::Kiwix.proxy("http://127.0.0.1:1", "/kiwix/search", "", open_timeout: 1)
|
|
end
|
|
|
|
# A path with non-ASCII characters, e.g. an article title like
|
|
# "/kiwix/content/wikipedia/Dlhé_diely" — exactly what Sinatra's splat
|
|
# hands .proxy for a real article link, already URL-*decoded*. Used to
|
|
# raise URI::InvalidComponentError (caught, so this returned nil — every
|
|
# article link 502'd) since URI#path= rejects raw UTF-8 bytes outright
|
|
# instead of escaping them.
|
|
def test_proxy_escapes_non_ascii_characters_in_the_path
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
port = server.addr[1]
|
|
request_line = nil
|
|
thread = Thread.new do
|
|
client = server.accept
|
|
request_line = client.gets
|
|
client.print("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 0\r\n\r\n")
|
|
client.close
|
|
end
|
|
|
|
response = Maptool::Kiwix.proxy("http://127.0.0.1:#{port}", "/kiwix/content/wikipedia/Dlhé_diely", "")
|
|
thread.join
|
|
server.close
|
|
|
|
assert_equal "GET /kiwix/content/wikipedia/Dlh%C3%A9_diely HTTP/1.1\r\n", request_line
|
|
assert_equal "200", response.code
|
|
end
|
|
|
|
# kiwix-serve's own geo search never echoes back a result's coordinates
|
|
# (verified live against a real ZIM) — .nearby fetches each matching
|
|
# article's page too and reads its <meta name="geo.position"> tag, so
|
|
# this stub server has to answer both the search request and each
|
|
# article-page request that follows, in turn.
|
|
def test_nearby_builds_a_feature_per_geotagged_search_result
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
port = server.addr[1]
|
|
search_xml = <<~XML
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss><channel>
|
|
<item><title>Foo</title><link>/kiwix/content/wikipedia/Foo</link></item>
|
|
<item><title>Bar</title><link>/kiwix/content/wikipedia/Bar</link></item>
|
|
</channel></rss>
|
|
XML
|
|
|
|
thread = Thread.new do
|
|
# 3, not 2: .nearby fetches every search result's own page to look
|
|
# for coordinates, including Bar's — which turns out to have none
|
|
# and gets filtered out, but the request still happens. Leaving this
|
|
# at 2 the first time round left that third connection's request
|
|
# unaccepted, and it hung for the full default read_timeout instead
|
|
# of failing fast.
|
|
3.times do
|
|
client = server.accept
|
|
request_line = client.gets
|
|
body =
|
|
if request_line.include?("/kiwix/search")
|
|
search_xml
|
|
elsif request_line.include?("/Foo")
|
|
'<meta name="geo.position" content="48.1;17.2">'
|
|
else
|
|
"no coordinates here"
|
|
end
|
|
client.print("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}")
|
|
client.close
|
|
end
|
|
end
|
|
|
|
geojson = Maptool::Kiwix.nearby("http://127.0.0.1:#{port}", [48.0, 17.0, 48.2, 17.3])
|
|
thread.join
|
|
server.close
|
|
|
|
assert_equal 1, geojson["features"].size, "Bar has no geo.position tag and must be skipped, not crash"
|
|
feature = geojson["features"].first
|
|
assert_equal "Foo", feature["properties"]["name"]
|
|
assert_equal [17.2, 48.1], feature["geometry"]["coordinates"]
|
|
end
|
|
|
|
def test_nearby_returns_an_empty_collection_when_the_search_itself_fails
|
|
geojson = Maptool::Kiwix.nearby("http://127.0.0.1:1", [48.0, 17.0, 48.2, 17.3], open_timeout: 1)
|
|
assert_equal({ "type" => "FeatureCollection", "features" => [] }, geojson)
|
|
end
|
|
end
|