require_relative "test_helper" require "maptool/pmtiles_builder" require "tmpdir" require "fileutils" # Network calls (Geofabrik downloads/HEADs, the mapterhorn manifest) are # never exercised here — same "keep this offline/deterministic" reasoning # test_helper.rb already gives for /api/plugins staying off the real # Overpass API — build_offline/build_terrain take download_extract/ # fetch_manifest precisely so tests can swap those out. Fake `pmtiles`, # `osmium`, and `java` binaries (PMTILES_BIN/OSMIUM_BIN/JAVA_BIN) stand in # for the real tools, logging each invocation's subcommand/first arg to # PMTILES_LOG so tests can assert on what actually got called (e.g. "was # merge invoked, and with what"). # # Country codes below are real ones from the vendored # lib/maptool/data/europe_countries.geojson (Slovakia, Iceland, Spain) — # chosen so their real bboxes fall cleanly inside/outside the fake # manifest's chunk extents below, without needing a second fake geometry # source. class PmtilesBuilderTest < Minitest::Test def setup @data_dir = Dir.mktmpdir("maptool-pmtiles-test-data") @bin_dir = Dir.mktmpdir("maptool-pmtiles-test-bin") @log = File.join(@bin_dir, "pmtiles.log") # Output path: $3 for `extract SOURCE OUTPUT --region=... --maxzoom=...` # and `convert INPUT OUTPUT` alike; the *last* arg for `merge INPUT_1 # [INPUT_2 ...] OUTPUT`, since its input count varies. @pmtiles_ok = File.join(@bin_dir, "pmtiles-ok") File.write(@pmtiles_ok, <<~SH) #!/bin/sh echo "$1 $2" >> "$PMTILES_LOG" case "$1" in merge) for last; do :; done echo fake > "$last" ;; *) echo fake > "$3" ;; esac SH File.chmod(0o755, @pmtiles_ok) @pmtiles_fail = File.join(@bin_dir, "pmtiles-fail") File.write(@pmtiles_fail, "#!/bin/sh\necho 'boom' >&2\nexit 1\n") File.chmod(0o755, @pmtiles_fail) # Mimics go-pmtiles' real extract failure mode: hundreds of progress # lines before the actual error — see command_output's tail-truncation. @pmtiles_fail_noisy = File.join(@bin_dir, "pmtiles-fail-noisy") File.write(@pmtiles_fail_noisy, <<~SH) #!/bin/sh i=0 while [ "$i" -lt 200 ]; do echo "fetching $i dirs, $i chunks, 1 requests" >&2 i=$((i + 1)) done echo "stream error: stream ID 9; INTERNAL_ERROR" >&2 exit 1 SH File.chmod(0o755, @pmtiles_fail_noisy) # osmium merge PBF1 PBF2 ... -o OUTPUT — output is always "-o"'s next # arg, unlike pmtiles' varying-position output above. @osmium_ok = File.join(@bin_dir, "osmium-ok") File.write(@osmium_ok, <<~SH) #!/bin/sh echo "osmium $1" >> "$PMTILES_LOG" prev="" for arg; do if [ "$prev" = "-o" ]; then echo fake > "$arg"; fi prev="$arg" done SH File.chmod(0o755, @osmium_ok) # Same shape as osmium-ok, but fails on tags-filter specifically (the # first subcommand HikingTrails.extract runs) while still succeeding # on merge — lets a single-country build (which never calls merge) # exercise "hiking-trails extraction failed" without also breaking # offline.pmtiles' own osmium use in a multi-country test. @osmium_fail_tags_filter = File.join(@bin_dir, "osmium-fail-tags-filter") File.write(@osmium_fail_tags_filter, <<~SH) #!/bin/sh echo "osmium $1" >> "$PMTILES_LOG" if [ "$1" = "tags-filter" ]; then echo "boom" >&2 exit 1 fi prev="" for arg; do if [ "$prev" = "-o" ]; then echo fake > "$arg"; fi prev="$arg" done SH File.chmod(0o755, @osmium_fail_tags_filter) # Stands in for `java -jar planetiler.jar --osm-path=... --output=OUT` # — PLANETILER_JAR itself is irrelevant here (this fake ignores -jar's # argument entirely), only JAVA_BIN needs pointing at it. @java_ok = File.join(@bin_dir, "java-ok") File.write(@java_ok, <<~SH) #!/bin/sh echo "planetiler ${3%%=*}" >> "$PMTILES_LOG" for arg; do case "$arg" in --output=*) echo fake > "${arg#--output=}" ;; esac done SH File.chmod(0o755, @java_ok) @java_fail = File.join(@bin_dir, "java-fail") File.write(@java_fail, "#!/bin/sh\necho 'boom' >&2\nexit 1\n") File.chmod(0o755, @java_fail) @original_pmtiles_bin = ENV["PMTILES_BIN"] @original_osmium_bin = ENV["OSMIUM_BIN"] @original_java_bin = ENV["JAVA_BIN"] ENV["PMTILES_LOG"] = @log ENV["OSMIUM_BIN"] = @osmium_ok ENV["JAVA_BIN"] = @java_ok Maptool::PmtilesBuilder.reset! end def teardown FileUtils.remove_entry(@data_dir) FileUtils.remove_entry(@bin_dir) ENV["PMTILES_BIN"] = @original_pmtiles_bin ENV["OSMIUM_BIN"] = @original_osmium_bin ENV["JAVA_BIN"] = @original_java_bin ENV.delete("PMTILES_LOG") Maptool::PmtilesBuilder.reset! end # download_extract stub: writes a marker file (same "fake" content the # pmtiles/osmium/java fixtures use) instead of hitting Geofabrik, and # returns a fixed Last-Modified so sidecar-meta assertions are stable. # `calls` (an Array, shared across builds if the caller wants it) records # every URL actually downloaded, for asserting the per-country cache # skipped (or didn't skip) a re-download. def fake_download(last_modified: "Wed, 10 Sep 2026 08:00:01 GMT", calls: nil) ->(url, dest) { calls << url if calls File.write(dest, "fake") last_modified } end def log_lines File.exist?(@log) ? File.readlines(@log, chomp: true) : [] end def select_regions(*codes) Maptool::PmtilesBuilder.set_selected_regions(@data_dir, codes) end # chunk-a covers Slovakia's real extent, chunk-b covers Iceland's — # Spain falls in neither, for the "no chunk intersects" case. def fake_manifest(chunk_a_md5: "md5-a") [ { "name" => "planet.pmtiles", "url" => "https://example.com/planet.pmtiles", "md5sum" => "md5-planet", "min_lon" => -180.0, "min_lat" => -85.0, "max_lon" => 180.0, "max_lat" => 85.0, "min_zoom" => 0, "max_zoom" => 12 }, { "name" => "chunk-a.pmtiles", "url" => "https://example.com/chunk-a.pmtiles", "md5sum" => chunk_a_md5, "min_lon" => 16.0, "min_lat" => 47.0, "max_lon" => 23.0, "max_lat" => 50.0, "min_zoom" => 13, "max_zoom" => 15 }, { "name" => "chunk-b.pmtiles", "url" => "https://example.com/chunk-b.pmtiles", "md5sum" => "md5-b", "min_lon" => -25.0, "min_lat" => 63.0, "max_lon" => -13.0, "max_lat" => 67.0, "min_zoom" => 13, "max_zoom" => 15 }, ] end def test_offline_and_terrain_status_start_idle assert_equal({ state: "idle" }, Maptool::PmtilesBuilder.offline_status) assert_equal({ state: "idle" }, Maptool::PmtilesBuilder.terrain_status) end # --- region selection --- def test_available_regions_includes_known_countries codes = Maptool::PmtilesBuilder.available_regions.map { |r| r[:code] } assert_includes codes, "SK" assert_includes codes, "IS" end def test_selected_regions_defaults_to_empty assert_empty Maptool::PmtilesBuilder.selected_regions(@data_dir) end def test_set_selected_regions_round_trips select_regions("SK", "AT") assert_equal ["SK", "AT"], Maptool::PmtilesBuilder.selected_regions(@data_dir) end def test_set_selected_regions_rejects_an_unknown_code assert_raises(Maptool::PmtilesBuilder::BuildError) { select_regions("SK", "XX") } end # --- offline.pmtiles --- def test_build_offline_raises_with_no_selection assert_raises(Maptool::PmtilesBuilder::BuildError) do Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) end end def test_build_offline_writes_the_file_and_reports_done select_regions("SK") thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join assert_path_exists File.join(@data_dir, "offline.pmtiles") refute_path_exists File.join(@data_dir, "offline.tmp.pmtiles") assert_equal "done", Maptool::PmtilesBuilder.offline_status[:state] assert_equal ["planetiler --osm-path", "osmium tags-filter", "osmium add-locations-to-ways", "osmium cat"], log_lines assert_equal( { "sources" => [{ "code" => "SK", "slug" => "europe/slovakia", "last_modified" => "Wed, 10 Sep 2026 08:00:01 GMT" }] }, Maptool::PmtilesBuilder.offline_meta(@data_dir), ) end def test_build_offline_also_writes_a_hiking_trails_geojson select_regions("SK") thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join # The fake osmium binary doesn't produce real OPL output, just # confirms the file lands and is valid (empty) GeoJSON — the real # parsing logic is covered by test_hiking_trails.rb against real OPL # fixtures. path = File.join(@data_dir, "hiking_trails.geojson") assert_path_exists path assert_equal({ "type" => "FeatureCollection", "features" => [] }, JSON.parse(File.read(path))) end # The actual reasoning: hiking trails are a bonus overlay bolted onto the # existing offline.pmtiles build, not the core map — an osmium hiccup on # this one step shouldn't take down an otherwise-successful build. def test_build_offline_still_succeeds_when_hiking_trails_extraction_fails select_regions("SK") ENV["OSMIUM_BIN"] = @osmium_fail_tags_filter thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join assert_path_exists File.join(@data_dir, "offline.pmtiles") assert_equal "done", Maptool::PmtilesBuilder.offline_status[:state] refute_path_exists File.join(@data_dir, "hiking_trails.geojson") refute_path_exists "#{File.join(@data_dir, "hiking_trails.geojson")}.tmp" end # Two countries selected — osmium merge should run before planetiler, # each downloaded source recorded in the sidecar meta. def test_build_offline_merges_multiple_selected_countries select_regions("SK", "AT") thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join assert_equal "done", Maptool::PmtilesBuilder.offline_status[:state] assert_equal( [ "osmium merge", "planetiler --osm-path", "osmium tags-filter", "osmium add-locations-to-ways", "osmium cat", # SK's own hiking-trails extraction "osmium tags-filter", "osmium add-locations-to-ways", "osmium cat", # AT's own ], log_lines, ) codes = Maptool::PmtilesBuilder.offline_meta(@data_dir)["sources"].map { |s| s["code"] } assert_equal %w[SK AT], codes end # The actual bug report this covers: hitting "Apply" after tweaking the # region selection used to re-download every selected country's .osm.pbf # from scratch, even ones already fetched by an earlier build. def test_build_offline_skips_redownloading_an_unchanged_cached_country select_regions("SK") calls = [] Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download(calls: calls)).join assert_equal 1, calls.size Maptool::PmtilesBuilder.build_offline( data_dir: @data_dir, download_extract: fake_download(calls: calls), fetch_last_modified: ->(_url) { "Wed, 10 Sep 2026 08:00:01 GMT" }, ).join assert_equal 1, calls.size assert_equal "done", Maptool::PmtilesBuilder.offline_status[:state] end def test_build_offline_redownloads_a_country_whose_extract_changed select_regions("SK") calls = [] Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download(calls: calls)).join Maptool::PmtilesBuilder.build_offline( data_dir: @data_dir, download_extract: fake_download(calls: calls, last_modified: "Thu, 11 Sep 2026 08:00:01 GMT"), fetch_last_modified: ->(_url) { "Thu, 11 Sep 2026 08:00:01 GMT" }, ).join assert_equal 2, calls.size end # A cache-freshness check that itself fails (Geofabrik unreachable) should # fall back to re-downloading rather than aborting the build. def test_build_offline_redownloads_when_the_cache_check_fails select_regions("SK") calls = [] Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download(calls: calls)).join fetch_last_modified = ->(_url) { raise Maptool::PmtilesBuilder::BuildError, "could not reach geofabrik" } Maptool::PmtilesBuilder.build_offline( data_dir: @data_dir, download_extract: fake_download(calls: calls), fetch_last_modified: fetch_last_modified, ).join assert_equal 2, calls.size assert_equal "done", Maptool::PmtilesBuilder.offline_status[:state] end def test_build_offline_only_downloads_a_newly_added_country select_regions("SK") calls = [] Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download(calls: calls)).join select_regions("SK", "AT") Maptool::PmtilesBuilder.build_offline( data_dir: @data_dir, download_extract: fake_download(calls: calls), fetch_last_modified: ->(_url) { "Wed, 10 Sep 2026 08:00:01 GMT" }, ).join assert_equal 1, calls.count { |url| url.include?("slovakia") } assert_equal 1, calls.count { |url| url.include?("austria") } end # offline_status[:step] should say which country is in flight right now # and which of the current build's selection are already done/still # pending — not just "running since X". def test_build_offline_reports_progress_step_while_running select_regions("SK", "AT") started = Queue.new gate = Queue.new download_extract = ->(url, dest) { started.push(url) gate.pop File.write(dest, "fake") "Wed, 10 Sep 2026 08:00:01 GMT" } thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: download_extract) started.pop assert_equal "fetching SK (1/2) — pending: AT", Maptool::PmtilesBuilder.offline_status[:step] gate.push(:go) started.pop assert_equal "fetching AT (2/2) — done: SK", Maptool::PmtilesBuilder.offline_status[:step] gate.push(:go) thread.join assert_nil Maptool::PmtilesBuilder.offline_status[:step] end def test_delete_offline_data_removes_the_file_meta_and_cache select_regions("SK") Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join assert_path_exists File.join(@data_dir, "offline.pmtiles") assert_path_exists File.join(@data_dir, "geofabrik_cache", "SK.osm.pbf") Maptool::PmtilesBuilder.delete_offline_data(@data_dir) refute_path_exists File.join(@data_dir, "offline.pmtiles") refute_path_exists File.join(@data_dir, "geofabrik_cache") assert_nil Maptool::PmtilesBuilder.offline_meta(@data_dir) end def test_delete_offline_data_is_a_noop_when_nothing_was_built Maptool::PmtilesBuilder.delete_offline_data(@data_dir) # should not raise end def test_delete_offline_data_raises_while_a_build_is_running select_regions("SK") gate = Queue.new thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: ->(_url, _dest) { gate.pop }) assert_raises(Maptool::PmtilesBuilder::BuildError) { Maptool::PmtilesBuilder.delete_offline_data(@data_dir) } ensure gate.push("unused") thread&.join end def test_delete_cached_extract_removes_only_that_country select_regions("SK", "AT") Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join Maptool::PmtilesBuilder.delete_cached_extract(@data_dir, "SK") refute_path_exists File.join(@data_dir, "geofabrik_cache", "SK.osm.pbf") assert_path_exists File.join(@data_dir, "geofabrik_cache", "AT.osm.pbf") end def test_delete_terrain_data_removes_the_file_and_meta select_regions("ES") ENV["PMTILES_BIN"] = @pmtiles_ok thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }) thread.join assert_path_exists File.join(@data_dir, "terrain.pmtiles") Maptool::PmtilesBuilder.delete_terrain_data(@data_dir) refute_path_exists File.join(@data_dir, "terrain.pmtiles") assert_nil Maptool::PmtilesBuilder.terrain_meta(@data_dir) end def test_cached_extracts_is_empty_before_any_download assert_empty Maptool::PmtilesBuilder.cached_extracts(@data_dir) end # Deliberately still selected — cached_extracts reflects the cache on # disk, not the current selection or the last build's own meta (see # cached_extracts' own comment for why those three can all disagree). def test_cached_extracts_lists_what_was_downloaded select_regions("SK", "AT") Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join extracts = Maptool::PmtilesBuilder.cached_extracts(@data_dir) assert_equal %w[AT SK], extracts.map { |e| e["code"] } assert_equal [4, 4], extracts.map { |e| e["size"] } # "fake".bytesize assert_equal ["Wed, 10 Sep 2026 08:00:01 GMT"] * 2, extracts.map { |e| e["last_modified"] } end def test_offline_meta_is_nil_before_any_build assert_nil Maptool::PmtilesBuilder.offline_meta(@data_dir) end def test_offline_update_available_is_nil_before_any_build assert_nil Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "unused" }) end def test_offline_update_available_is_false_when_unchanged select_regions("SK") Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join refute Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "Wed, 10 Sep 2026 08:00:01 GMT" }) end def test_offline_update_available_is_true_when_a_newer_extract_exists select_regions("SK") Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join assert Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "Thu, 11 Sep 2026 08:00:01 GMT" }) end # Same recorded source, but the selection has since grown — recorded # sources no longer match what the current selection would need. def test_offline_update_available_is_true_when_the_selection_grew select_regions("SK") Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join select_regions("SK", "AT") assert Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: ->(_url) { "Wed, 10 Sep 2026 08:00:01 GMT" }) end def test_offline_update_available_is_nil_when_geofabrik_is_unreachable select_regions("SK") Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download).join fetch_last_modified = ->(_url) { raise Maptool::PmtilesBuilder::BuildError, "could not reach geofabrik" } assert_nil Maptool::PmtilesBuilder.offline_update_available?(@data_dir, fetch_last_modified: fetch_last_modified) end def test_build_offline_reports_an_error_status_on_non_zero_exit select_regions("SK") ENV["JAVA_BIN"] = @java_fail thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join refute_path_exists File.join(@data_dir, "offline.pmtiles") status = Maptool::PmtilesBuilder.offline_status assert_equal "error", status[:state] assert_match(/exited with status/, status[:message]) end def test_build_offline_reports_a_clear_error_when_planetiler_is_missing select_regions("SK") ENV["JAVA_BIN"] = File.join(@bin_dir, "does-not-exist") thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) thread.join status = Maptool::PmtilesBuilder.offline_status assert_equal "error", status[:state] assert_match(/Java and planetiler\.jar installed/, status[:message]) end # download_extract blocks on a Queue so the build is deterministically # still "running" when the second call happens, rather than racing a real # one to see which state wins. def test_build_offline_raises_when_one_is_already_running select_regions("SK") gate = Queue.new thread = Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: ->(_url, _dest) { gate.pop }) assert_raises(Maptool::PmtilesBuilder::BuildError) do Maptool::PmtilesBuilder.build_offline(data_dir: @data_dir, download_extract: fake_download) end ensure gate.push("Wed, 10 Sep 2026 08:00:01 GMT") thread&.join end # --- terrain.pmtiles --- def test_build_terrain_raises_with_no_selection assert_raises(Maptool::PmtilesBuilder::BuildError) do Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }) end end # Spain intersects neither fake chunk — only planet.pmtiles matches, so # this should be a single extract straight to the final path, no merge. def test_build_terrain_extracts_only_planet_when_no_chunk_intersects select_regions("ES") ENV["PMTILES_BIN"] = @pmtiles_ok thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }) thread.join assert_path_exists File.join(@data_dir, "terrain.pmtiles") assert_equal "done", Maptool::PmtilesBuilder.terrain_status[:state] assert_equal ["extract https://example.com/planet.pmtiles"], log_lines end # Slovakia intersects chunk-a only — planet.pmtiles (always) plus that # one chunk get extracted, then merged. def test_build_terrain_extracts_and_merges_a_single_intersecting_chunk select_regions("SK") ENV["PMTILES_BIN"] = @pmtiles_ok thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }) thread.join assert_path_exists File.join(@data_dir, "terrain.pmtiles") assert_equal "done", Maptool::PmtilesBuilder.terrain_status[:state] extracted = log_lines.select { |line| line.start_with?("extract ") } merged = log_lines.select { |line| line.start_with?("merge ") } assert_equal ["extract https://example.com/planet.pmtiles", "extract https://example.com/chunk-a.pmtiles"], extracted assert_equal 1, merged.size assert_equal( { "sources" => [{ "name" => "planet.pmtiles", "md5sum" => "md5-planet" }, { "name" => "chunk-a.pmtiles", "md5sum" => "md5-a" }] }, Maptool::PmtilesBuilder.terrain_meta(@data_dir), ) end def test_terrain_meta_is_nil_before_any_build assert_nil Maptool::PmtilesBuilder.terrain_meta(@data_dir) end def test_terrain_update_available_is_false_when_unchanged select_regions("SK") ENV["PMTILES_BIN"] = @pmtiles_ok Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }).join refute Maptool::PmtilesBuilder.terrain_update_available?(@data_dir, fetch_manifest: -> { fake_manifest }) end # Same selection, but chunk-a's md5sum has since changed upstream. def test_terrain_update_available_is_true_when_a_source_changed select_regions("SK") ENV["PMTILES_BIN"] = @pmtiles_ok Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }).join assert Maptool::PmtilesBuilder.terrain_update_available?(@data_dir, fetch_manifest: -> { fake_manifest(chunk_a_md5: "md5-a-v2") }) end # Same recorded sources, but the selection has since grown to also cover # chunk-b — recorded sources no longer match what the current selection # would need. def test_terrain_update_available_is_true_when_the_selection_grew select_regions("SK") ENV["PMTILES_BIN"] = @pmtiles_ok Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }).join select_regions("SK", "IS") assert Maptool::PmtilesBuilder.terrain_update_available?(@data_dir, fetch_manifest: -> { fake_manifest }) end # Slovakia + Iceland together intersect both fake chunks — planet + both # chunks, one 3-way merge. def test_build_terrain_merges_multiple_intersecting_chunks select_regions("SK", "IS") ENV["PMTILES_BIN"] = @pmtiles_ok thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }) thread.join assert_equal "done", Maptool::PmtilesBuilder.terrain_status[:state] extracted = log_lines.count { |line| line.start_with?("extract ") } assert_equal 3, extracted # planet + chunk-a + chunk-b assert_equal 1, log_lines.count { |line| line.start_with?("merge ") } end def test_build_terrain_reports_a_clear_error_when_the_manifest_is_missing_planet select_regions("ES") ENV["PMTILES_BIN"] = @pmtiles_ok thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest.reject { |i| i["name"] == "planet.pmtiles" } }) thread.join status = Maptool::PmtilesBuilder.terrain_status assert_equal "error", status[:state] assert_match(/missing planet\.pmtiles/, status[:message]) end def test_build_terrain_reports_an_error_status_on_non_zero_exit select_regions("ES") ENV["PMTILES_BIN"] = @pmtiles_fail thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }) thread.join refute_path_exists File.join(@data_dir, "terrain.pmtiles") status = Maptool::PmtilesBuilder.terrain_status assert_equal "error", status[:state] assert_match(/exited with status/, status[:message]) end # The real trigger for this: a Mapterhorn extract that failed after ~7000 # progress lines, producing a 500k+ character status[:message] that was # unreadable in the web UI. def test_build_terrain_truncates_a_noisy_command_failure select_regions("ES") ENV["PMTILES_BIN"] = @pmtiles_fail_noisy thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { fake_manifest }) thread.join status = Maptool::PmtilesBuilder.terrain_status assert_equal "error", status[:state] assert_match(/\(\d+ earlier lines omitted\)/, status[:message]) assert_match(/stream error: stream ID 9/, status[:message]) assert_operator status[:message].length, :<, 5000 end def test_build_terrain_raises_when_one_is_already_running select_regions("ES") ENV["PMTILES_BIN"] = @pmtiles_ok gate = Queue.new thread = Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { gate.pop }) assert_raises(Maptool::PmtilesBuilder::BuildError) do Maptool::PmtilesBuilder.build_terrain(data_dir: @data_dir, fetch_manifest: -> { "unused" }) end ensure gate.push(fake_manifest) thread&.join end end