require_relative "test_helper" require "maptool/plugins" require "tmpdir" require "fileutils" class PluginsTest < Minitest::Test def setup @dir = Dir.mktmpdir("maptool-plugins-test") @plugin_dir = File.join(@dir, "hello") FileUtils.mkdir_p(@plugin_dir) File.write(File.join(@plugin_dir, "plugin.json"), JSON.generate(label: "Hello", command: "./run.sh")) File.write(File.join(@plugin_dir, "run.sh"), "#!/bin/sh\necho '{\"type\":\"FeatureCollection\",\"features\":[]}'\n") File.chmod(0o755, File.join(@plugin_dir, "run.sh")) end def teardown FileUtils.remove_entry(@dir) end def test_list_finds_plugins_by_their_manifest assert_equal [{ "name" => "hello", "label" => "Hello" }], Maptool::Plugins.list(@dir) end def test_list_is_empty_for_a_directory_with_no_plugins assert_empty Maptool::Plugins.list(Dir.mktmpdir) end def test_run_returns_the_plugins_geojson_output result = Maptool::Plugins.run(@dir, "hello", [1, 2, 3, 4]) assert_equal({ "type" => "FeatureCollection", "features" => [] }, result) end def test_run_raises_for_an_unknown_plugin assert_raises(Maptool::Plugins::RunError) { Maptool::Plugins.run(@dir, "nope", [1, 2, 3, 4]) } end # A directory-name-shaped ".." would otherwise resolve one level above # dir via File.join — the same class of path-escape GpxStore guards # against for uploaded file paths. def test_run_rejects_a_path_traversal_attempt_as_the_name assert_raises(Maptool::Plugins::RunError) { Maptool::Plugins.run(@dir, "..", [1, 2, 3, 4]) } end def test_run_raises_when_the_script_exits_non_zero File.write(File.join(@plugin_dir, "run.sh"), "#!/bin/sh\nexit 1\n") File.chmod(0o755, File.join(@plugin_dir, "run.sh")) assert_raises(Maptool::Plugins::RunError) { Maptool::Plugins.run(@dir, "hello", [1, 2, 3, 4]) } end def test_run_raises_when_the_script_does_not_print_a_feature_collection File.write(File.join(@plugin_dir, "run.sh"), "#!/bin/sh\necho 'not json'\n") File.chmod(0o755, File.join(@plugin_dir, "run.sh")) assert_raises(Maptool::Plugins::RunError) { Maptool::Plugins.run(@dir, "hello", [1, 2, 3, 4]) } end # Multiple dirs (app.rb's PLUGINS_DIRS: the built-in plugins/ plus an # optional private one under data/plugins/) — list/run both accept an # array, not just the single-dir form the tests above exercise. def test_list_merges_plugins_across_several_dirs other_dir = Dir.mktmpdir("maptool-plugins-test-2") other_plugin_dir = File.join(other_dir, "private") FileUtils.mkdir_p(other_plugin_dir) File.write(File.join(other_plugin_dir, "plugin.json"), JSON.generate(label: "Private", command: "./run.sh")) plugins = Maptool::Plugins.list([@dir, other_dir]) assert_equal [{ "name" => "hello", "label" => "Hello" }, { "name" => "private", "label" => "Private" }], plugins ensure FileUtils.remove_entry(other_dir) end def test_list_prefers_the_first_dir_when_the_same_name_exists_in_both other_dir = Dir.mktmpdir("maptool-plugins-test-2") FileUtils.mkdir_p(File.join(other_dir, "hello")) File.write(File.join(other_dir, "hello", "plugin.json"), JSON.generate(label: "Shadowed", command: "./run.sh")) plugins = Maptool::Plugins.list([@dir, other_dir]) assert_equal [{ "name" => "hello", "label" => "Hello" }], plugins ensure FileUtils.remove_entry(other_dir) end def test_run_finds_a_plugin_in_a_later_dir_when_earlier_dirs_do_not_have_it result = Maptool::Plugins.run([Dir.mktmpdir, @dir], "hello", [1, 2, 3, 4]) assert_equal({ "type" => "FeatureCollection", "features" => [] }, result) end end