From adc89f7977ae4b1e86cb413fb52406865150b064 Mon Sep 17 00:00:00 2001
From: rubenwardy <rw@rubenwardy.com>
Date: Mon, 15 Aug 2022 09:08:24 +0100
Subject: [PATCH] Add unit tests for pkgmgr.install_dir

---
 builtin/mainmenu/pkgmgr.lua            |  10 +-
 builtin/mainmenu/tests/pkgmgr_spec.lua | 243 +++++++++++++++++++++++++
 2 files changed, 247 insertions(+), 6 deletions(-)
 create mode 100644 builtin/mainmenu/tests/pkgmgr_spec.lua

diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua
index 3028295c8..9fa9aebb0 100644
--- a/builtin/mainmenu/pkgmgr.lua
+++ b/builtin/mainmenu/pkgmgr.lua
@@ -244,11 +244,7 @@ end
 
 --------------------------------------------------------------------------------
 function pkgmgr.is_valid_modname(modpath)
-	if modpath:find("-") ~= nil then
-		return false
-	end
-
-	return true
+	return modpath:match("[^a-z0-9_]") == nil
 end
 
 --------------------------------------------------------------------------------
@@ -521,6 +517,8 @@ function pkgmgr.install_dir(expected_type, path, basename, targetpath)
 	local basefolder = pkgmgr.get_base_folder(path)
 
 	if expected_type == "txp" then
+		assert(basename)
+
 		-- There's no good way to detect a texture pack, so let's just assume
 		-- it's correct for now.
 		if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
@@ -544,7 +542,7 @@ function pkgmgr.install_dir(expected_type, path, basename, targetpath)
 
 	-- Check type
 	if basefolder.type ~= expected_type and (basefolder.type ~= "modpack" or expected_type ~= "mod") then
-		return nil, fgettext("Unable to install a $1 as a $1", basefolder.type, expected_type)
+		return nil, fgettext("Unable to install a $1 as a $2", basefolder.type, expected_type)
 	end
 
 	-- Set targetpath if not predetermined
diff --git a/builtin/mainmenu/tests/pkgmgr_spec.lua b/builtin/mainmenu/tests/pkgmgr_spec.lua
new file mode 100644
index 000000000..558fae9fb
--- /dev/null
+++ b/builtin/mainmenu/tests/pkgmgr_spec.lua
@@ -0,0 +1,243 @@
+--Minetest
+--Copyright (C) 2022 rubenwardy
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local mods_dir = "/tmp/.minetest/mods"
+local games_dir = "/tmp/.minetest/games"
+local txp_dir = "/tmp/.minetest/textures"
+
+local function reset()
+	local env = {
+		core = {},
+		unpack = table.unpack or unpack,
+		pkgmgr = {},
+		DIR_DELIM = "/",
+	}
+	env._G = env
+	setmetatable(env, { __index = _G })
+
+	local core = env.core
+
+	local calls = {}
+	function core.get_games()
+		return {}
+	end
+	function core.delete_dir(...)
+		table.insert(calls, { "delete_dir", ... })
+		return true
+	end
+	function core.copy_dir(...)
+		table.insert(calls, { "copy_dir", ... })
+		return true
+	end
+	function core.get_texturepath()
+		return txp_dir
+	end
+	function core.get_modpath()
+		return mods_dir
+	end
+	function core.get_gamepath()
+		return games_dir
+	end
+	function env.fgettext(fmt, ...)
+		return fmt
+	end
+
+	setfenv(loadfile("builtin/common/misc_helpers.lua"), env)()
+	setfenv(loadfile("builtin/mainmenu/pkgmgr.lua"), env)()
+
+	function env.pkgmgr.update_gamelist()
+		table.insert(calls, { "update_gamelist" })
+	end
+	function env.pkgmgr.refresh_globals()
+		table.insert(calls, { "refresh_globals" })
+	end
+
+	function env.assert_calls(list)
+		assert.are.same(list, calls)
+	end
+
+	return env
+end
+
+
+describe("install_dir", function()
+	it("installs texture pack", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "invalid", path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "mytxp", nil)
+		assert.is.equal(txp_dir .. "/mytxp", path)
+		assert.is._nil(message)
+		env.assert_calls({
+			{ "delete_dir", txp_dir .. "/mytxp" },
+			{ "copy_dir", "/tmp/123", txp_dir .. "/mytxp", false },
+		})
+	end)
+
+	it("prevents installing other as texture pack", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "mod", path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "mytxp", nil)
+		assert.is._nil(path)
+		assert.is.equal("Unable to install a $1 as a texture pack", message)
+	end)
+
+	it("installs mod", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "mod", path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "mymod", nil)
+		assert.is.equal(mods_dir .. "/mymod", path)
+		assert.is._nil(message)
+		env.assert_calls({
+			{ "delete_dir", mods_dir .. "/mymod" },
+			{ "copy_dir", "/tmp/123", mods_dir .. "/mymod", false },
+			{ "refresh_globals" },
+		})
+	end)
+
+	it("installs modpack", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "modpack", path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "mymod", nil)
+		assert.is.equal(mods_dir .. "/mymod", path)
+		assert.is._nil(message)
+		env.assert_calls({
+			{ "delete_dir", mods_dir .. "/mymod" },
+			{ "copy_dir", "/tmp/123", mods_dir .. "/mymod", false },
+			{ "refresh_globals" },
+		})
+	end)
+
+	it("installs game", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "game", path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("game", "/tmp/123", "mygame", nil)
+		assert.is.equal(games_dir .. "/mygame", path)
+		assert.is._nil(message)
+		env.assert_calls({
+			{ "delete_dir", games_dir .. "/mygame" },
+			{ "copy_dir", "/tmp/123", games_dir .. "/mygame", false },
+			{ "update_gamelist" },
+		})
+	end)
+
+	it("installs mod detects name", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "mod", path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", nil, nil)
+		assert.is.equal(mods_dir .. "/123", path)
+		assert.is._nil(message)
+		env.assert_calls({
+			{ "delete_dir", mods_dir .. "/123" },
+			{ "copy_dir", "/tmp/123", mods_dir .. "/123", false },
+			{ "refresh_globals" },
+		})
+	end)
+
+	it("installs mod detects invalid name", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "mod", path = "/tmp/hi!" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("mod", "/tmp/hi!", nil, nil)
+		assert.is._nil(path)
+		assert.is.equal("Install: Unable to find suitable folder name for $1", message)
+	end)
+
+	it("installs mod to target (update)", function()
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = "mod", path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "mymod", "/tmp/alt-target")
+		assert.is.equal("/tmp/alt-target", path)
+		assert.is._nil(message)
+		env.assert_calls({
+			{ "delete_dir", "/tmp/alt-target" },
+			{ "copy_dir", "/tmp/123", "/tmp/alt-target", false },
+			{ "refresh_globals" },
+		})
+	end)
+
+	it("checks expected types", function()
+		local actual_type = "modpack"
+		local env = reset()
+		env.pkgmgr.get_base_folder = function()
+			return { type = actual_type, path = "/tmp/123" }
+		end
+
+		local path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "name", nil)
+		assert.is._not._nil(path)
+		assert.is._nil(message)
+
+		path, message = env.pkgmgr.install_dir("game", "/tmp/123", "name", nil)
+		assert.is._nil(path)
+		assert.is.equal("Unable to install a $1 as a $2", message)
+
+		path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil)
+		assert.is._nil(path)
+		assert.is.equal("Unable to install a $1 as a texture pack", message)
+
+		actual_type = "game"
+
+		path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "name", nil)
+		assert.is._nil(path)
+		assert.is.equal("Unable to install a $1 as a $2", message)
+
+		path, message = env.pkgmgr.install_dir("game", "/tmp/123", "name", nil)
+		assert.is._not._nil(path)
+		assert.is._nil(message)
+
+		path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil)
+		assert.is._nil(path)
+		assert.is.equal("Unable to install a $1 as a texture pack", message)
+
+		actual_type = "txp"
+
+		path, message = env.pkgmgr.install_dir("mod", "/tmp/123", "name", nil)
+		assert.is._nil(path)
+		assert.is.equal("Unable to install a $1 as a $2", message)
+
+		path, message = env.pkgmgr.install_dir("game", "/tmp/123", "name", nil)
+		assert.is._nil(path)
+		assert.is.equal("Unable to install a $1 as a $2", message)
+
+		path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil)
+		assert.is._not._nil(path)
+		assert.is._nil(message)
+
+	end)
+end)