Skip to content

Commit

Permalink
feat: validate commands to execute only the Tutor ones
Browse files Browse the repository at this point in the history
* refactor: execute the commands validation before any command is executed
* test: fix tests to run with the latest changes
  • Loading branch information
bra-i-am committed Apr 1, 2024
1 parent 059ace1 commit a943433
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 55 deletions.
57 changes: 39 additions & 18 deletions tests/distro/run_extra_commands/application/test_run_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,52 @@ def test_valid_tutor_command():
This test verifies that are executed all the extra commands successfully.
"""
# Given
tutor_commands_manager = TestTutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager)

valid_tutor_commands = [
"command with word tutor 1",
"command with word tutor 2",
"command with word tutor 3",
]

tutor_commands_manager = TestTutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=valid_tutor_commands)

# When
for command in valid_tutor_commands:
run_tutor_command(command=command)

assert tutor_commands_manager.commands_ran == len(valid_tutor_commands)


def test_invalid_tutor_command():
def test_invalid_or_misspelled_tutor_command():
"""
Test running invalid commands.
This test verifies that the execution fails when is
intended to execute invalid extra commands.
"""
# Given
tutor_commands_manager = TestTutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager)

invalid_tutor_command = "pip command 1"
invalid_tutor_command = [
"pip command 1",
"tutor command && pip command 2",
"tutor command & pip command 3",
"tutor command || pip command 4",
"tutor command | pip command 5",
"tutor command ; pip command 6",
]

# When
with pytest.raises(CommandError) as command_error:
run_tutor_command(command=invalid_tutor_command)
tutor_commands_manager = TestTutorCommandManager()
CommandsRunner(commands_manager=tutor_commands_manager, commands=invalid_tutor_command)

assert command_error.type is CommandError
assert "is not a valid Tutor command" in command_error.value.args[0]

splitted_commands = [tutor_commands_manager.split_command(command) for command in invalid_tutor_command]
commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ")

pip_commands_sent = commands_word_by_word.count("pip")
pip_commands_found = command_error.value.args[0].split(" ").count("pip")

assert pip_commands_sent == pip_commands_found


def test_misspelled_tutor_command():
Expand All @@ -61,14 +72,24 @@ def test_misspelled_tutor_command():
a misspelled Tutor command.
"""
# Given
tutor_commands_manager = TestTutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager)

invalid_tutor_command = "totur command bad written"
misspelled_commands = [
"totur command 1",
"totur command 2",
"totur command 3",
"totur command 4",
"totur command 5",
]

# When
with pytest.raises(CommandError) as command_error:
run_tutor_command(command=invalid_tutor_command)
tutor_commands_manager = TestTutorCommandManager()
CommandsRunner(commands_manager=tutor_commands_manager, commands=misspelled_commands)

assert command_error.type is CommandError
assert "you have a typo" in command_error.value.args[0]

splitted_commands = [tutor_commands_manager.split_command(command) for command in misspelled_commands]
commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ")

misspelled_commands_sent = commands_word_by_word.count("totur")
misspelled_commands_found = command_error.value.args[0].split(" ").count("totur")

assert misspelled_commands_sent == misspelled_commands_found
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
Test tutor commands functions.
"""

from tutordistro.distro.extra_commands.domain.command_manager import CommandManager
from tutordistro.distro.share.domain.command_error import CommandError
from tutordistro.utils.constants import find_tutor_misspelled
from tutordistro.distro.extra_commands.infrastructure.tutor_commands import TutorCommandManager


class TestTutorCommandManager(CommandManager):
class TestTutorCommandManager(TutorCommandManager):
"""
Executes a Tutor command for testing.
Expand All @@ -25,18 +23,5 @@ def run_command(self, command: str):
Args:
command (str): Testing command.
"""
is_tutor_misspelled = find_tutor_misspelled(command)

if "tutor" not in command.lower():
if is_tutor_misspelled:
raise CommandError(
f'Maybe you have a typo using the command "{command}"'
)
raise CommandError(
f'''
Command "{command}" is not a valid Tutor command.
Take the official Tutor commands into account
https://docs.tutor.edly.io/reference/cli/index.html
'''
)

self.commands_ran += 1
7 changes: 4 additions & 3 deletions tutordistro/commands/run_extra_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ def run_extra_commands():
.strip()
)
config = tutor_config.load(directory)
distro_extra_commands = config.get("DISTRO_EXTRA_COMMANDS", None)

tutor_commands_manager = TutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager)
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=distro_extra_commands)

if config.get("DISTRO_EXTRA_COMMANDS"):
for command in config["DISTRO_EXTRA_COMMANDS"]:
if distro_extra_commands:
for command in distro_extra_commands:
run_tutor_command(command=command)
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ class CommandsRunner:
commands_manager (ThemeRepository): The command manager to use for executing the extra command.
"""

def __init__(self, commands_manager: CommandManager):
def __init__(self, commands_manager: CommandManager, commands: list[str]):
self.commands_manager = commands_manager
commands_manager.validate_commands(commands)

def __call__(self, command: str):
"""
Expand Down
70 changes: 55 additions & 15 deletions tutordistro/distro/extra_commands/infrastructure/tutor_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
Distro tutor command functions.
"""

import re
import subprocess

from tutordistro.distro.extra_commands.domain.command_manager import CommandManager
from tutordistro.distro.share.domain.command_error import CommandError
from tutordistro.utils.constants import find_tutor_misspelled
from tutordistro.utils.constants import COMMAND_CHAINING_OPERATORS, create_regex_from_array, find_tutor_misspelled


class TutorCommandManager(CommandManager):
Expand All @@ -18,6 +19,58 @@ class TutorCommandManager(CommandManager):
Args:
CommandManager (class): Base command manager class.
"""
def split_command(self, command: str):
"""
Takes a command that is wanted to be split according to some
bash command chaining operators
Args:
command (str): Command with command chaining operator
Return:
The command split into an array
"""
return re.split(create_regex_from_array(COMMAND_CHAINING_OPERATORS), command)

def validate_commands(self, commands: list[str] | None):
"""
Takes all the extra commands sent through config.yml and verifies that
all the commands are correct before executing them
Args:
commands (list[str] | None): The commands sent through DISTRO_EXTRA_COMMANDS in config.yml
"""
if not commands:
raise CommandError(
"No commands found in the DISTRO_EXTRA_COMMANDS attribute of the config.yml file."
)

splitted_commands = [self.split_command(command) for command in commands]
flat_commands_array = sum(splitted_commands, [])

invalid_commands = []
misspelled_commands = []
for command in flat_commands_array:
if "tutor" not in command.lower():
if find_tutor_misspelled(command):
misspelled_commands.append(command)
else:
invalid_commands.append(command)

if invalid_commands or misspelled_commands:
raise CommandError(
f"""
Error: Were found some issues with the commands:
{'=> Invalid commands: ' if invalid_commands else ""}
{', '.join(invalid_commands)}
{'=> Misspelled commands: ' if misspelled_commands else ""}
{', '.join(misspelled_commands)}
Take a look of the official Tutor commands: https://docs.tutor.edly.io/reference/cli/index.html
"""
)

def run_command(self, command: str):
"""
Expand All @@ -29,20 +82,6 @@ def run_command(self, command: str):
command (str): Tutor command.
"""
try:
is_tutor_misspelled = find_tutor_misspelled(command)

if "tutor" not in command.lower():
if is_tutor_misspelled:
raise CommandError(
f'Maybe you have a typo using the command "{command}"'
)
raise CommandError(
f"""
Command "{command}" is not a valid Tutor command.
Take the official Tutor commands into account
https://docs.tutor.edly.io/reference/cli/index.html
"""
)
with subprocess.Popen(
command,
shell=True,
Expand All @@ -56,6 +95,7 @@ def run_command(self, command: str):
raise CommandError(
f'Error running command "{command}".\n{error.decode()}'
)
# This print is left on purpose to show the command output
print(output.decode())

except subprocess.SubprocessError as error:
Expand Down
18 changes: 18 additions & 0 deletions tutordistro/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""
import re

COMMAND_CHAINING_OPERATORS = ["&&", "&", "||", "|", ";"]


def find_tutor_misspelled(command: str):
"""
Expand All @@ -15,3 +17,19 @@ def find_tutor_misspelled(command: str):
If its found the word 'tutor' misspelled is returned True
"""
return re.match(r'[tT](?:[oru]{3}|[oru]{2}[rR]|[oru]u?)', command)


def create_regex_from_array(arr: list[str]):
"""
This functions compiles a new regex turning taking care of
escaping special characters
Args:
arr (list[str]): String that would be used to create a new regex
Return:
A new compiled regex pattern that can be used for comparisons
"""
escaped_arr = [re.escape(item) for item in arr]
regex_pattern = "|".join(escaped_arr)
return re.compile(regex_pattern)

0 comments on commit a943433

Please sign in to comment.