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 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 Foo/kiwix/content/wikipedia/Foo Bar/kiwix/content/wikipedia/Bar 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") '' 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