mirror of
https://github.com/ChristianLight/tutor.git
synced 2024-11-16 10:05:18 +00:00
4dc772d1e4
`upgrade` had several issues, which are summarized here: https://discuss.overhang.io/t/confusing-instructions-during-upgrade/2281/7 - The docs say that you should run quickstart, but what most people will see is the big command tutor local upgrade --from=lilac verbatim paragraph. - The local upgrade command should be very explicit about the fact that users need to run quickstart. - Maybe the name of the local upgrade command should be improved. - When upgrading tutor from one major release to the next, there should be a more explicit warning to inform users of what they are doing (see this other conversation 1) - We should tell people that they almost certainly need to enable the tutor and the mfe plugins, if they are not enabled during upgrade. - A link to all of the breaking changes from the changelog should be prominently displayed during upgrade. - The docs should emphasize that upgrading from one major release to the next is potentially a risky endeavor and that downgrading is not possible. The docs should also link to the changelog. This commit has grown slightly beyond the intended scope, but the changes should be mostly positive.
252 lines
9.8 KiB
Python
252 lines
9.8 KiB
Python
import os
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
from tutor.__about__ import __version__
|
|
from tutor import config as tutor_config
|
|
from tutor import env, exceptions, fmt
|
|
from tutor.types import Config
|
|
from tests.helpers import temporary_root
|
|
|
|
|
|
class EnvTests(unittest.TestCase):
|
|
def test_walk_templates(self) -> None:
|
|
renderer = env.Renderer({}, [env.TEMPLATES_ROOT])
|
|
templates = list(renderer.walk_templates("local"))
|
|
self.assertIn("local/docker-compose.yml", templates)
|
|
|
|
def test_walk_templates_partials_are_ignored(self) -> None:
|
|
template_name = "apps/openedx/settings/partials/common_all.py"
|
|
renderer = env.Renderer({}, [env.TEMPLATES_ROOT], ignore_folders=["partials"])
|
|
templates = list(renderer.walk_templates("apps"))
|
|
self.assertIn(template_name, renderer.environment.loader.list_templates())
|
|
self.assertNotIn(template_name, templates)
|
|
|
|
def test_is_binary_file(self) -> None:
|
|
self.assertTrue(env.is_binary_file("/home/somefile.ico"))
|
|
|
|
def test_find_os_path(self) -> None:
|
|
renderer = env.Renderer({}, [env.TEMPLATES_ROOT])
|
|
path = renderer.find_os_path("local/docker-compose.yml")
|
|
self.assertTrue(os.path.exists(path))
|
|
|
|
def test_pathjoin(self) -> None:
|
|
with temporary_root() as root:
|
|
self.assertEqual(
|
|
os.path.join(env.base_dir(root), "dummy"), env.pathjoin(root, "dummy")
|
|
)
|
|
|
|
def test_render_str(self) -> None:
|
|
self.assertEqual(
|
|
"hello world", env.render_str({"name": "world"}, "hello {{ name }}")
|
|
)
|
|
|
|
def test_render_unknown(self) -> None:
|
|
config: Config = {
|
|
"var1": "a",
|
|
}
|
|
self.assertEqual("ab", env.render_unknown(config, "{{ var1 }}b"))
|
|
self.assertEqual({"x": "ac"}, env.render_unknown(config, {"x": "{{ var1 }}c"}))
|
|
|
|
def test_common_domain(self) -> None:
|
|
self.assertEqual(
|
|
"mydomain.com",
|
|
env.render_str(
|
|
{"d1": "d1.mydomain.com", "d2": "d2.mydomain.com"},
|
|
"{{ d1|common_domain(d2) }}",
|
|
),
|
|
)
|
|
|
|
def test_render_str_missing_configuration(self) -> None:
|
|
self.assertRaises(exceptions.TutorError, env.render_str, {}, "hello {{ name }}")
|
|
|
|
def test_render_file(self) -> None:
|
|
config: Config = {}
|
|
tutor_config.update_with_base(config)
|
|
tutor_config.update_with_defaults(config)
|
|
tutor_config.render_full(config)
|
|
|
|
config["MYSQL_ROOT_PASSWORD"] = "testpassword"
|
|
rendered = env.render_file(config, "hooks", "mysql", "init")
|
|
self.assertIn("testpassword", rendered)
|
|
|
|
@patch.object(tutor_config.fmt, "echo")
|
|
def test_render_file_missing_configuration(self, _: Mock) -> None:
|
|
self.assertRaises(
|
|
exceptions.TutorError, env.render_file, {}, "local", "docker-compose.yml"
|
|
)
|
|
|
|
def test_save_full(self) -> None:
|
|
with temporary_root() as root:
|
|
config = tutor_config.load_full(root)
|
|
with patch.object(fmt, "STDOUT"):
|
|
env.save(root, config)
|
|
self.assertTrue(
|
|
os.path.exists(
|
|
os.path.join(env.base_dir(root), "local", "docker-compose.yml")
|
|
)
|
|
)
|
|
|
|
def test_save_full_with_https(self) -> None:
|
|
with temporary_root() as root:
|
|
config = tutor_config.load_full(root)
|
|
config["ENABLE_HTTPS"] = True
|
|
with patch.object(fmt, "STDOUT"):
|
|
env.save(root, config)
|
|
with open(
|
|
os.path.join(env.base_dir(root), "apps", "caddy", "Caddyfile"),
|
|
encoding="utf-8",
|
|
) as f:
|
|
self.assertIn("www.myopenedx.com{$default_site_port}", f.read())
|
|
|
|
def test_patch(self) -> None:
|
|
patches = {"plugin1": "abcd", "plugin2": "efgh"}
|
|
with patch.object(
|
|
env.plugins, "iter_patches", return_value=patches.items()
|
|
) as mock_iter_patches:
|
|
rendered = env.render_str({}, '{{ patch("location") }}')
|
|
mock_iter_patches.assert_called_once_with({}, "location")
|
|
self.assertEqual("abcd\nefgh", rendered)
|
|
|
|
def test_patch_separator_suffix(self) -> None:
|
|
patches = {"plugin1": "abcd", "plugin2": "efgh"}
|
|
with patch.object(env.plugins, "iter_patches", return_value=patches.items()):
|
|
rendered = env.render_str(
|
|
{}, '{{ patch("location", separator=",\n", suffix=",") }}'
|
|
)
|
|
self.assertEqual("abcd,\nefgh,", rendered)
|
|
|
|
def test_plugin_templates(self) -> None:
|
|
with tempfile.TemporaryDirectory() as plugin_templates:
|
|
# Create plugin
|
|
plugin1 = env.plugins.DictPlugin(
|
|
{"name": "plugin1", "version": "0", "templates": plugin_templates}
|
|
)
|
|
|
|
# Create two templates
|
|
os.makedirs(os.path.join(plugin_templates, "plugin1", "apps"))
|
|
with open(
|
|
os.path.join(plugin_templates, "plugin1", "unrendered.txt"),
|
|
"w",
|
|
encoding="utf-8",
|
|
) as f:
|
|
f.write("This file should not be rendered")
|
|
with open(
|
|
os.path.join(plugin_templates, "plugin1", "apps", "rendered.txt"),
|
|
"w",
|
|
encoding="utf-8",
|
|
) as f:
|
|
f.write("Hello my ID is {{ ID }}")
|
|
|
|
# Create configuration
|
|
config: Config = {"ID": "abcd"}
|
|
|
|
# Render templates
|
|
with patch.object(
|
|
env.plugins,
|
|
"iter_enabled",
|
|
return_value=[plugin1],
|
|
):
|
|
with temporary_root() as root:
|
|
# Render plugin templates
|
|
env.save_plugin_templates(plugin1, root, config)
|
|
|
|
# Check that plugin template was rendered
|
|
dst_unrendered = os.path.join(
|
|
root, "env", "plugins", "plugin1", "unrendered.txt"
|
|
)
|
|
dst_rendered = os.path.join(
|
|
root, "env", "plugins", "plugin1", "apps", "rendered.txt"
|
|
)
|
|
self.assertFalse(os.path.exists(dst_unrendered))
|
|
self.assertTrue(os.path.exists(dst_rendered))
|
|
with open(dst_rendered, encoding="utf-8") as f:
|
|
self.assertEqual("Hello my ID is abcd", f.read())
|
|
|
|
def test_renderer_is_reset_on_config_change(self) -> None:
|
|
with tempfile.TemporaryDirectory() as plugin_templates:
|
|
plugin1 = env.plugins.DictPlugin(
|
|
{"name": "plugin1", "version": "0", "templates": plugin_templates}
|
|
)
|
|
# Create one template
|
|
os.makedirs(os.path.join(plugin_templates, plugin1.name))
|
|
with open(
|
|
os.path.join(plugin_templates, plugin1.name, "myplugin.txt"),
|
|
"w",
|
|
encoding="utf-8",
|
|
) as f:
|
|
f.write("some content")
|
|
|
|
# Load env once
|
|
config: Config = {"PLUGINS": []}
|
|
env1 = env.Renderer.instance(config).environment
|
|
|
|
with patch.object(
|
|
env.plugins,
|
|
"iter_enabled",
|
|
return_value=[plugin1],
|
|
):
|
|
# Load env a second time
|
|
config["PLUGINS"] = ["myplugin"]
|
|
env2 = env.Renderer.instance(config).environment
|
|
|
|
self.assertNotIn("plugin1/myplugin.txt", env1.loader.list_templates())
|
|
self.assertIn("plugin1/myplugin.txt", env2.loader.list_templates())
|
|
|
|
def test_iter_values_named(self) -> None:
|
|
config: Config = {
|
|
"something0_test_app": 0,
|
|
"something1_test_not_app": 1,
|
|
"notsomething_test_app": 2,
|
|
"something3_test_app": 3,
|
|
}
|
|
renderer = env.Renderer.instance(config)
|
|
self.assertEqual([2, 3], list(renderer.iter_values_named(suffix="test_app")))
|
|
self.assertEqual([1, 3], list(renderer.iter_values_named(prefix="something")))
|
|
self.assertEqual(
|
|
[0, 3],
|
|
list(
|
|
renderer.iter_values_named(
|
|
prefix="something", suffix="test_app", allow_empty=True
|
|
)
|
|
),
|
|
)
|
|
|
|
|
|
class CurrentVersionTests(unittest.TestCase):
|
|
def test_current_version_in_empty_env(self) -> None:
|
|
with temporary_root() as root:
|
|
self.assertIsNone(env.current_version(root))
|
|
self.assertIsNone(env.get_env_release(root))
|
|
self.assertIsNone(env.should_upgrade_from_release(root))
|
|
self.assertTrue(env.is_up_to_date(root))
|
|
|
|
def test_current_version_in_lilac_env(self) -> None:
|
|
with temporary_root() as root:
|
|
os.makedirs(env.base_dir(root))
|
|
with open(
|
|
os.path.join(env.base_dir(root), env.VERSION_FILENAME),
|
|
"w",
|
|
encoding="utf-8",
|
|
) as f:
|
|
f.write("12.0.46")
|
|
self.assertEqual("12.0.46", env.current_version(root))
|
|
self.assertEqual("lilac", env.get_env_release(root))
|
|
self.assertEqual("lilac", env.should_upgrade_from_release(root))
|
|
self.assertFalse(env.is_up_to_date(root))
|
|
|
|
def test_current_version_in_latest_env(self) -> None:
|
|
with temporary_root() as root:
|
|
os.makedirs(env.base_dir(root))
|
|
with open(
|
|
os.path.join(env.base_dir(root), env.VERSION_FILENAME),
|
|
"w",
|
|
encoding="utf-8",
|
|
) as f:
|
|
f.write(__version__)
|
|
self.assertEqual(__version__, env.current_version(root))
|
|
self.assertEqual("maple", env.get_env_release(root))
|
|
self.assertIsNone(env.should_upgrade_from_release(root))
|
|
self.assertTrue(env.is_up_to_date(root))
|