Replace a for append loop with list extend. Create the list with values instead of creating an empty list and extending it with another list.
223 lines
6.4 KiB
Python
223 lines
6.4 KiB
Python
import ast
|
|
import copy
|
|
import glob
|
|
import os
|
|
import pathlib
|
|
import shutil
|
|
import unittest
|
|
from io import StringIO
|
|
from unittest.mock import patch
|
|
|
|
import git
|
|
import yaml
|
|
|
|
import frappe
|
|
from frappe.modules.patch_handler import get_all_patches, parse_as_configfile
|
|
from frappe.utils.boilerplate import (
|
|
PatchCreator,
|
|
_create_app_boilerplate,
|
|
_get_user_inputs,
|
|
github_workflow_template,
|
|
)
|
|
|
|
|
|
class TestBoilerPlate(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.default_hooks = frappe._dict(
|
|
{
|
|
"app_name": "test_app",
|
|
"app_title": "Test App",
|
|
"app_description": "This app's description contains 'single quotes' and \"double quotes\".",
|
|
"app_publisher": "Test Publisher",
|
|
"app_email": "example@example.org",
|
|
"app_license": "MIT",
|
|
"create_github_workflow": False,
|
|
}
|
|
)
|
|
|
|
cls.default_user_input = frappe._dict(
|
|
{
|
|
"title": "Test App",
|
|
"description": "This app's description contains 'single quotes' and \"double quotes\".",
|
|
"publisher": "Test Publisher",
|
|
"email": "example@example.org",
|
|
"icon": "", # empty -> default
|
|
"color": "",
|
|
"app_license": "MIT",
|
|
"github_workflow": "n",
|
|
}
|
|
)
|
|
|
|
cls.bench_path = frappe.utils.get_bench_path()
|
|
cls.apps_dir = os.path.join(cls.bench_path, "apps")
|
|
cls.gitignore_file = ".gitignore"
|
|
cls.git_folder = ".git"
|
|
|
|
cls.root_paths = [
|
|
"README.md",
|
|
"pyproject.toml",
|
|
"license.txt",
|
|
cls.git_folder,
|
|
cls.gitignore_file,
|
|
]
|
|
cls.paths_inside_app = [
|
|
"__init__.py",
|
|
"hooks.py",
|
|
"patches.txt",
|
|
"templates",
|
|
"www",
|
|
"config",
|
|
"modules.txt",
|
|
"public",
|
|
]
|
|
|
|
def create_app(self, hooks, no_git=False):
|
|
self.addCleanup(self.delete_test_app, hooks.app_name)
|
|
_create_app_boilerplate(self.apps_dir, hooks, no_git)
|
|
|
|
@classmethod
|
|
def delete_test_app(cls, app_name):
|
|
test_app_dir = os.path.join(cls.bench_path, "apps", app_name)
|
|
if os.path.exists(test_app_dir):
|
|
shutil.rmtree(test_app_dir)
|
|
|
|
@staticmethod
|
|
def get_user_input_stream(inputs):
|
|
user_inputs = []
|
|
for value in inputs.values():
|
|
if isinstance(value, list):
|
|
user_inputs.extend(value)
|
|
else:
|
|
user_inputs.append(value)
|
|
return StringIO("\n".join(user_inputs))
|
|
|
|
def test_simple_input_to_boilerplate(self):
|
|
with patch("sys.stdin", self.get_user_input_stream(self.default_user_input)):
|
|
hooks = _get_user_inputs(self.default_hooks.app_name)
|
|
self.assertDictEqual(hooks, self.default_hooks)
|
|
|
|
def test_invalid_inputs(self):
|
|
invalid_inputs = copy.copy(self.default_user_input).update(
|
|
{
|
|
"title": ["1nvalid Title", "valid title"],
|
|
}
|
|
)
|
|
with patch("sys.stdin", self.get_user_input_stream(invalid_inputs)):
|
|
hooks = _get_user_inputs(self.default_hooks.app_name)
|
|
self.assertEqual(hooks.app_title, "valid title")
|
|
|
|
def test_valid_ci_yaml(self):
|
|
yaml.safe_load(github_workflow_template.format(**self.default_hooks))
|
|
|
|
@unittest.skipUnless(
|
|
os.access(frappe.get_app_path("frappe"), os.W_OK), "Only run if frappe app paths is writable"
|
|
)
|
|
def test_create_app(self):
|
|
app_name = "test_app"
|
|
|
|
hooks = frappe._dict(
|
|
{
|
|
"app_name": app_name,
|
|
"app_title": "Test App",
|
|
"app_description": "This app's description contains 'single quotes' and \"double quotes\".",
|
|
"app_publisher": "Test Publisher",
|
|
"app_email": "example@example.org",
|
|
"app_license": "MIT",
|
|
}
|
|
)
|
|
|
|
self.create_app(hooks)
|
|
new_app_dir = os.path.join(self.bench_path, self.apps_dir, app_name)
|
|
|
|
paths = self.get_paths(new_app_dir, app_name)
|
|
for path in paths:
|
|
self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {app_name} app")
|
|
|
|
self.check_parsable_python_files(new_app_dir)
|
|
|
|
app_repo = git.Repo(new_app_dir)
|
|
self.assertEqual(app_repo.active_branch.name, "develop")
|
|
|
|
patches_file = os.path.join(new_app_dir, app_name, "patches.txt")
|
|
self.assertTrue(os.path.exists(patches_file), msg=f"{patches_file} not found")
|
|
|
|
self.assertEqual(parse_as_configfile(patches_file), [])
|
|
|
|
@unittest.skipUnless(
|
|
os.access(frappe.get_app_path("frappe"), os.W_OK), "Only run if frappe app paths is writable"
|
|
)
|
|
def test_create_app_without_git_init(self):
|
|
app_name = "test_app_no_git"
|
|
|
|
hooks = frappe._dict(
|
|
{
|
|
"app_name": app_name,
|
|
"app_title": "Test App",
|
|
"app_description": "This app's description contains 'single quotes' and \"double quotes\".",
|
|
"app_publisher": "Test Publisher",
|
|
"app_email": "example@example.org",
|
|
"app_license": "MIT",
|
|
}
|
|
)
|
|
self.create_app(hooks, no_git=True)
|
|
|
|
new_app_dir = os.path.join(self.apps_dir, app_name)
|
|
|
|
paths = self.get_paths(new_app_dir, app_name)
|
|
for path in paths:
|
|
if os.path.basename(path) in (self.git_folder, self.gitignore_file):
|
|
self.assertFalse(os.path.exists(path), msg=f"{path} shouldn't exist in {app_name} app")
|
|
else:
|
|
self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {app_name} app")
|
|
|
|
self.check_parsable_python_files(new_app_dir)
|
|
|
|
def get_paths(self, app_dir, app_name):
|
|
all_paths = [os.path.join(app_dir, path) for path in self.root_paths]
|
|
all_paths.append(os.path.join(app_dir, app_name))
|
|
all_paths.extend(os.path.join(app_dir, app_name, path) for path in self.paths_inside_app)
|
|
|
|
return all_paths
|
|
|
|
def check_parsable_python_files(self, app_dir):
|
|
# check if python files are parsable
|
|
python_files = glob.glob(app_dir + "**/*.py", recursive=True)
|
|
|
|
for python_file in python_files:
|
|
with open(python_file) as p:
|
|
try:
|
|
ast.parse(p.read())
|
|
except Exception as e:
|
|
self.fail(f"Can't parse python file in new app: {python_file}\n" + str(e))
|
|
|
|
@unittest.skipUnless(
|
|
os.access(frappe.get_app_path("frappe"), os.W_OK), "Only run if frappe app paths is writable"
|
|
)
|
|
def test_new_patch_util(self):
|
|
user_inputs = {
|
|
"app_name": "frappe",
|
|
"doctype": "User",
|
|
"docstring": "Delete all users",
|
|
"file_name": "", # Accept default
|
|
"patch_folder_confirmation": "Y",
|
|
}
|
|
|
|
patches_txt = pathlib.Path(pathlib.Path(frappe.get_app_path("frappe", "patches.txt")))
|
|
original_patches = patches_txt.read_text()
|
|
|
|
with patch("sys.stdin", self.get_user_input_stream(user_inputs)):
|
|
patch_creator = PatchCreator()
|
|
patch_creator.fetch_user_inputs()
|
|
patch_creator.create_patch_file()
|
|
|
|
patches = get_all_patches()
|
|
expected_patch = "frappe.core.doctype.user.patches.delete_all_users"
|
|
self.assertIn(expected_patch, patches)
|
|
|
|
self.assertTrue(patch_creator.patch_file.exists())
|
|
|
|
# Cleanup
|
|
shutil.rmtree(patch_creator.patch_file.parents[0])
|
|
patches_txt.write_text(original_patches)
|