diff --git a/.flake8 b/.flake8 index 210b338..c2798ab 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,2 @@ [flake8] -exclude = grace/generators/templates/* +exclude = grace/generators/templates/ diff --git a/.github/workflows/grace_framework.yml b/.github/workflows/grace_framework.yml index e1a67a1..4617d83 100644 --- a/.github/workflows/grace_framework.yml +++ b/.github/workflows/grace_framework.yml @@ -26,14 +26,13 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install . - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 grace --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 grace --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest + pytest -v diff --git a/grace/cli.py b/grace/cli.py index f3167e9..5cf8187 100644 --- a/grace/cli.py +++ b/grace/cli.py @@ -6,8 +6,6 @@ from click import group, argument, option, pass_context from grace.generator import register_generators -import os -import sys APP_INFO = """ | Discord.py version: {discord_version} diff --git a/grace/generator.py b/grace/generator.py index 5972356..a823549 100644 --- a/grace/generator.py +++ b/grace/generator.py @@ -2,7 +2,7 @@ from pathlib import Path from grace.importer import import_package_modules from cookiecutter.main import cookiecutter -from grace.exceptions import ValidationError +from grace.exceptions import GeneratorError, ValidationError def register_generators(command_group: Group): @@ -33,7 +33,7 @@ class Generator(Command): def __init__(self): if not self.NAME: - raise ValueError("Generator name must be defined.") + raise GeneratorError("Generator name must be defined.") super().__init__(self.NAME, callback=self._generate, **self.OPTIONS) diff --git a/grace/generators/project_generator.py b/grace/generators/project_generator.py index 4b01a1d..ed0bc35 100644 --- a/grace/generators/project_generator.py +++ b/grace/generators/project_generator.py @@ -9,7 +9,7 @@ class ProjectGenerator(Generator): "hidden": True } - def generate(self, name, database=True): + def generate(self, name: str, database: bool = True): info(f"Creating '{name}'") self.generate_template(self.NAME, values={ @@ -18,9 +18,25 @@ def generate(self, name, database=True): "database": "yes" if database else "no" }) - def validate(self, name, **_kwargs): - return match('([a-z]|[0-9]|-)+', name) + def validate(self, name: str, **_kwargs) -> bool: + """Validate the project name. + A valid project name must: + - contain only lowercase letters, numbers, and hyphens -def generator(): + Example: + - "awesome-project" is valid + - "awesomeproject" is valid + - "awesome-project1" is valid + - "my-awesome-project" is valid + + - "awesomeProject" is invalid + - "AwesomeProject" is invalid + - "awesome_project" is invalid + - "myAwesomeproject12" is invalid + """ + return bool(match('([a-z]|[0-9]|-)+', name)) + + +def generator() -> Generator: return ProjectGenerator() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 5eccca1..5c5ec5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,8 @@ dependencies = [ "cookiecutter", "mypy", "pytest", + "flake8", + "pytest-mock", "coverage", ] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/generators/test_project_generator.py b/tests/generators/test_project_generator.py new file mode 100644 index 0000000..d9a9605 --- /dev/null +++ b/tests/generators/test_project_generator.py @@ -0,0 +1,55 @@ +import pytest +from grace.generator import Generator +from grace.generators.project_generator import ProjectGenerator + + +@pytest.fixture +def generator(): + return ProjectGenerator() + + +def test_generate_project_with_database(mocker, generator): + """Test if the generate method creates the correct template with a database.""" + mock_generate_template = mocker.patch.object(Generator, 'generate_template') + name = "example-project" + + generator.generate(name, database=True) + + mock_generate_template.assert_called_once_with('project', values={ + 'project_name': name, + 'project_description': '', + 'database': 'yes' + }) + + +def test_generate_project_without_database(mocker, generator): + """Test if the generate method creates the correct template without a database.""" + mock_generate_template = mocker.patch.object(Generator, 'generate_template') + name = "example-project" + + generator.generate(name, database=False) + + mock_generate_template.assert_called_once_with('project', values={ + 'project_name': name, + 'project_description': '', + 'database': 'no' + }) + + +def test_validate_valid_name(generator): + """Test if the validate method passes for a valid project name.""" + valid_name = "example-project" + assert generator.validate(valid_name) == True + + +def test_validate_invalid_name_no_hyphen(generator): + """Test if the validate method raises ValueError for name without a hyphen.""" + invalid_name = "ExampleProject" + assert generator.validate(invalid_name) == False + + +def test_validate_invalid_name_uppercase(generator): + """Test if the validate method raises ValueError for uppercase name.""" + invalid_name = "Example-Project" + assert generator.validate(invalid_name) == False + diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..bb305e4 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,52 @@ +import pytest +from grace.config import Config + + +@pytest.fixture +def config(): + return Config() + + +def test_set_environment(config): + """Test if the environment is set correctly""" + config.set_environment("test") + + assert config.current_environment == "test" + + +# def test_section_name(config): +# """Test if the section name is set correctly""" +# config.set_environment("test") + +# assert config.environment.name == "test" + + +# def test_database(config): +# config.set_environment("test") + +# assert config.database["adapter"] == "sqlite" +# assert config.database["database"] == "grace_test.db" + + +# def test_database_uri(config): +# from sqlalchemy.engine import URL + +# config.set_environment("test") + +# assert config.database_uri == URL.create(drivername="sqlite", database="grace_test.db") + + +# def test_get(config): +# config.set_environment("test") + +# assert config.get("test", "test") == "test" +# assert config.get("test", "test_int") == 42 +# assert config.get("test", "test_float") == 42.5 +# assert config.get("test", "test_bool") is True +# assert config.get("test", "test_fallback") is None + + +# def test_client(config): +# config.set_environment("test") + +# assert config.client is not None \ No newline at end of file diff --git a/tests/test_generator.py b/tests/test_generator.py new file mode 100644 index 0000000..80568a5 --- /dev/null +++ b/tests/test_generator.py @@ -0,0 +1,60 @@ +import pytest +from unittest.mock import patch, MagicMock +from grace.generator import Generator +from grace.exceptions import ValidationError +from grace.generator import register_generators + + +class MockGenerator(Generator): + NAME = 'mock' + + +@pytest.fixture +def generator(): + return MockGenerator() + + +def test_generator(generator): + """Test if the generator is initialized correctly""" + assert generator.NAME == 'mock' + assert generator.OPTIONS == {} + + +def test_validate(generator): + """Test if the generator validate method returns True""" + assert generator.validate() == True + + +def test_generate_template(generator): + """Test if the generator generate_template method calls cookiecutter with the correct arguments""" + with patch('grace.generator.cookiecutter') as cookiecutter: + generator.generate_template('project', values={}) + template_path = str(generator.templates_path / 'project') + cookiecutter.assert_called_once_with(template_path, extra_context={}, no_input=True) + + +def test_generate(generator): + """Test if the generator generate method raises a NotImplementedError""" + with pytest.raises(NotImplementedError): + generator.generate() + + +def test_register_generators(): + """Test if the register_generators function registers all the generators""" + with patch('grace.generator.import_package_modules') as import_package_modules: + import_package_modules.return_value = [MagicMock(generator=MagicMock())] + command_group = MagicMock() + register_generators(command_group) + command_group.add_command.assert_called_once() + import_package_modules.assert_called_once() + from grace import generators + import_package_modules.assert_called_with(generators, shallow=False) + + +def test_generate_validate(generator): + """Test if the generator _generate method raises a ValidationError""" + with patch('grace.generator.Generator.validate') as validate: + validate.return_value = False + with pytest.raises(ValidationError): + generator._generate() + validate.assert_called_once() \ No newline at end of file