From 60343c179e016ea28c7a631ecaa538c694a43972 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 26 Jul 2025 02:28:22 +0000 Subject: [PATCH 1/4] Add generate-schema command - Implement GenerateSchemaCommand class to generate JSON schema for available structures - Add command line arguments for structures path and output file - Scan both contribs directory and custom structures path - Generate JSON schema with PluginList enum containing all available structures - Support output to file or stdout - Add documentation for the generate-schema command --- struct_module/commands/generate_schema.py | 66 +++++++++++++++++++++++ struct_module/main.py | 2 + 2 files changed, 68 insertions(+) create mode 100644 struct_module/commands/generate_schema.py diff --git a/struct_module/commands/generate_schema.py b/struct_module/commands/generate_schema.py new file mode 100644 index 0000000..3e3fb4e --- /dev/null +++ b/struct_module/commands/generate_schema.py @@ -0,0 +1,66 @@ +from struct_module.commands import Command +import os +import json + +# Generate Schema command class +class GenerateSchemaCommand(Command): + def __init__(self, parser): + super().__init__(parser) + parser.add_argument('-s', '--structures-path', type=str, help='Path to structure definitions') + parser.add_argument('-o', '--output', type=str, help='Output file path for the schema (default: stdout)') + parser.set_defaults(func=self.execute) + + def execute(self, args): + self.logger.info("Generating JSON schema for available structures") + self._generate_schema(args) + + def _generate_schema(self, args): + # Get the path to contribs directory (built-in structures) + this_file = os.path.dirname(os.path.realpath(__file__)) + contribs_path = os.path.join(this_file, "..", "contribs") + + # Determine paths to scan + if args.structures_path: + final_path = args.structures_path + paths_to_list = [final_path, contribs_path] + else: + paths_to_list = [contribs_path] + + # Collect all available structures + all_structures = set() + for path in paths_to_list: + if os.path.exists(path): + for root, _, files in os.walk(path): + for file in files: + if file.endswith(".yaml"): + file_path = os.path.join(root, file) + rel_path = os.path.relpath(file_path, path) + # Remove .yaml extension + rel_path = rel_path[:-5] + all_structures.add(rel_path) + + # Create JSON schema + schema = { + "definitions": { + "PluginList": { + "enum": sorted(list(all_structures)) + } + } + } + + # Convert to JSON string + json_output = json.dumps(schema, indent=2) + + # Output to file or stdout + if args.output: + # Create output directory if it doesn't exist + output_dir = os.path.dirname(args.output) + if output_dir and not os.path.exists(output_dir): + os.makedirs(output_dir) + + with open(args.output, 'w') as f: + f.write(json_output) + self.logger.info(f"Schema written to {args.output}") + print(f"✅ Schema successfully generated at: {args.output}") + else: + print(json_output) diff --git a/struct_module/main.py b/struct_module/main.py index 041f667..13ef759 100644 --- a/struct_module/main.py +++ b/struct_module/main.py @@ -6,6 +6,7 @@ from struct_module.commands.info import InfoCommand from struct_module.commands.validate import ValidateCommand from struct_module.commands.list import ListCommand +from struct_module.commands.generate_schema import GenerateSchemaCommand from struct_module.logging_config import configure_logging @@ -26,6 +27,7 @@ def main(): ValidateCommand(subparsers.add_parser('validate', help='Validate the YAML configuration file')) GenerateCommand(subparsers.add_parser('generate', help='Generate the project structure')) ListCommand(subparsers.add_parser('list', help='List available structures')) + GenerateSchemaCommand(subparsers.add_parser('generate-schema', help='Generate JSON schema for available structures')) argcomplete.autocomplete(parser) From d4168b7156bcce6fefa4c8b4bb90f66642d665db Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 26 Jul 2025 14:43:06 +0000 Subject: [PATCH 2/4] Add tests for GenerateSchemaCommand initialization and execution --- tests/test_commands.py | 265 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 1 deletion(-) diff --git a/tests/test_commands.py b/tests/test_commands.py index 627ea2c..e0360e2 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -4,7 +4,10 @@ from struct_module.commands.info import InfoCommand from struct_module.commands.validate import ValidateCommand from struct_module.commands.list import ListCommand +from struct_module.commands.generate_schema import GenerateSchemaCommand import argparse +import json +import os @pytest.fixture def parser(): @@ -53,7 +56,6 @@ def test_validate_command(parser): mock_validate_folders.assert_called_once() mock_validate_variables.assert_called_once() - def test_with_value_renders_jinja2_with_mappings(): from struct_module.template_renderer import TemplateRenderer config_variables = [] @@ -75,3 +77,264 @@ def test_with_value_renders_jinja2_with_mappings(): context['mappings'] = mappings or {} rendered_with[k] = renderer.render_template(str(v), context) assert rendered_with["team"] == "devops-team" + + +# Tests for GenerateSchemaCommand +def test_generate_schema_command_init(parser): + """Test that GenerateSchemaCommand initializes correctly with proper arguments.""" + command = GenerateSchemaCommand(parser) + + # Check that arguments were added + actions = {action.dest: action for action in parser._actions} + assert 'structures_path' in actions + assert 'output' in actions + + # Check help text + assert actions['structures_path'].help == 'Path to structure definitions' + assert actions['output'].help == 'Output file path for the schema (default: stdout)' + + +def test_generate_schema_command_execute_calls_generate_schema(parser): + """Test that execute method calls _generate_schema.""" + command = GenerateSchemaCommand(parser) + args = MagicMock() + + with patch.object(command, '_generate_schema') as mock_generate_schema: + command.execute(args) + mock_generate_schema.assert_called_once_with(args) + + +def test_generate_schema_stdout_output(parser): + """Test generate schema command outputs to stdout when no output file specified.""" + command = GenerateSchemaCommand(parser) + args = MagicMock() + args.structures_path = None + args.output = None + + # Mock the file system to simulate contribs directory with YAML files + mock_walk_data = [ + ('/path/to/contribs', [], ['terraform-module.yaml', 'docker-files.yaml']), + ('/path/to/contribs/subdir', [], ['nested-struct.yaml']) + ] + + with patch('os.path.dirname') as mock_dirname, \ + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join') as mock_join, \ + patch('os.path.exists', return_value=True), \ + patch('os.walk', return_value=mock_walk_data), \ + patch('os.path.relpath', side_effect=['terraform-module', 'docker-files', 'subdir/nested-struct']), \ + patch('builtins.print') as mock_print: + + mock_dirname.return_value = '/path/to/commands' + mock_realpath.return_value = '/path/to/commands' + mock_join.return_value = '/path/to/contribs' + + command._generate_schema(args) + + # Verify print was called with JSON output + mock_print.assert_called_once() + printed_output = mock_print.call_args[0][0] + + # Parse the JSON to verify structure + schema = json.loads(printed_output) + assert 'definitions' in schema + assert 'PluginList' in schema['definitions'] + assert 'enum' in schema['definitions']['PluginList'] + + # Check that structures are sorted and include expected files + structures = schema['definitions']['PluginList']['enum'] + assert 'docker-files' in structures + assert 'subdir/nested-struct' in structures + assert 'terraform-module' in structures + assert structures == sorted(structures) # Verify sorted order + + +def test_generate_schema_file_output(parser): + """Test generate schema command writes to file when output path specified.""" + command = GenerateSchemaCommand(parser) + args = MagicMock() + args.structures_path = None + args.output = '/output/schema.json' + + mock_walk_data = [ + ('/path/to/contribs', [], ['test-struct.yaml']) + ] + + mock_file = MagicMock() + + with patch('os.path.dirname') as mock_dirname, \ + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join') as mock_join, \ + patch('os.path.exists', return_value=True), \ + patch('os.walk', return_value=mock_walk_data), \ + patch('os.path.relpath', return_value='test-struct'), \ + patch('os.makedirs') as mock_makedirs, \ + patch('builtins.open', return_value=mock_file) as mock_open, \ + patch('builtins.print') as mock_print: + + mock_dirname.side_effect = ['/path/to/commands', '/output'] + mock_realpath.return_value = '/path/to/commands' + mock_join.return_value = '/path/to/contribs' + mock_file.__enter__.return_value = mock_file + + command._generate_schema(args) + + # Verify file operations + mock_makedirs.assert_called_once_with('/output') + mock_open.assert_called_once_with('/output/schema.json', 'w') + mock_file.write.assert_called_once() + + # Verify success message + mock_print.assert_called_once_with( + '✅ Schema successfully generated at: /output/schema.json') + + # Verify JSON content written to file + written_content = mock_file.write.call_args[0][0] + schema = json.loads(written_content) + assert 'definitions' in schema + assert 'PluginList' in schema['definitions'] + assert 'test-struct' in schema['definitions']['PluginList']['enum'] + + +def test_generate_schema_with_custom_structures_path(parser): + """Test generate schema command with custom structures path.""" + command = GenerateSchemaCommand(parser) + args = MagicMock() + args.structures_path = '/custom/structures' + args.output = None + + # Mock two separate walk calls for custom path and contribs + def mock_walk_side_effect(path): + if '/custom/structures' in path: + return [('/custom/structures', [], ['custom-struct.yaml'])] + else: # contribs path + return [('/path/to/contribs', [], ['builtin-struct.yaml'])] + + def mock_relpath_side_effect(file_path, base_path): + if 'custom-struct' in file_path: + return 'custom-struct' + else: + return 'builtin-struct' + + with patch('os.path.dirname') as mock_dirname, \ + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join') as mock_join, \ + patch('os.path.exists', return_value=True), \ + patch('os.walk', side_effect=mock_walk_side_effect), \ + patch('os.path.relpath', side_effect=mock_relpath_side_effect), \ + patch('builtins.print') as mock_print: + + mock_dirname.return_value = '/path/to/commands' + mock_realpath.return_value = '/path/to/commands' + mock_join.return_value = '/path/to/contribs' + + command._generate_schema(args) + + # Verify print was called + mock_print.assert_called_once() + printed_output = mock_print.call_args[0][0] + + # Parse and verify both custom and builtin structures are included + schema = json.loads(printed_output) + structures = schema['definitions']['PluginList']['enum'] + assert 'custom-struct' in structures + assert 'builtin-struct' in structures + + +def test_generate_schema_no_output_directory_creation(parser): + """Test that output directory is not created when it already exists.""" + command = GenerateSchemaCommand(parser) + args = MagicMock() + args.structures_path = None + args.output = '/existing/dir/schema.json' + + mock_walk_data = [('/path/to/contribs', [], ['test.yaml'])] + mock_file = MagicMock() + + with patch('os.path.dirname') as mock_dirname, \ + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join') as mock_join, \ + patch('os.path.exists', side_effect=lambda path: True), \ + patch('os.walk', return_value=mock_walk_data), \ + patch('os.path.relpath', return_value='test'), \ + patch('os.makedirs') as mock_makedirs, \ + patch('builtins.open', return_value=mock_file) as mock_open, \ + patch('builtins.print'): + + mock_dirname.side_effect = ['/path/to/commands', '/existing/dir'] + mock_realpath.return_value = '/path/to/commands' + mock_join.return_value = '/path/to/contribs' + mock_file.__enter__.return_value = mock_file + + command._generate_schema(args) + + # Verify makedirs was not called since directory exists + mock_makedirs.assert_not_called() + + +def test_generate_schema_empty_directory(parser): + """Test generate schema command with directory containing no YAML files.""" + command = GenerateSchemaCommand(parser) + args = MagicMock() + args.structures_path = None + args.output = None + + # Empty directory + mock_walk_data = [('/path/to/contribs', [], [])] + + with patch('os.path.dirname') as mock_dirname, \ + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join') as mock_join, \ + patch('os.path.exists', return_value=True), \ + patch('os.walk', return_value=mock_walk_data), \ + patch('builtins.print') as mock_print: + + mock_dirname.return_value = '/path/to/commands' + mock_realpath.return_value = '/path/to/commands' + mock_join.return_value = '/path/to/contribs' + + command._generate_schema(args) + + # Verify empty enum is generated + printed_output = mock_print.call_args[0][0] + schema = json.loads(printed_output) + assert schema['definitions']['PluginList']['enum'] == [] + + +def test_generate_schema_nonexistent_path(parser): + """Test generate schema command with nonexistent path.""" + command = GenerateSchemaCommand(parser) + args = MagicMock() + args.structures_path = '/nonexistent/path' + args.output = None + + # Mock contribs path with some files + def mock_walk_side_effect(path): + if '/nonexistent/path' in path: + return [] # No files for nonexistent path + else: # contribs path + return [('/path/to/contribs', [], ['builtin.yaml'])] + + def mock_exists_side_effect(path): + return '/nonexistent/path' not in path + + with patch('os.path.dirname') as mock_dirname, \ + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join') as mock_join, \ + patch('os.path.exists', side_effect=mock_exists_side_effect), \ + patch('os.walk', side_effect=mock_walk_side_effect), \ + patch('os.path.relpath', return_value='builtin'), \ + patch('builtins.print') as mock_print: + + mock_dirname.return_value = '/path/to/commands' + mock_realpath.return_value = '/path/to/commands' + mock_join.return_value = '/path/to/contribs' + + command._generate_schema(args) + + # Should only include builtin structures, not fail + printed_output = mock_print.call_args[0][0] + schema = json.loads(printed_output) + structures = schema['definitions']['PluginList']['enum'] + assert 'builtin' in structures + assert len(structures) == 1 From bbd9fae664e81fcb50e925ce68d09f6430e11f63 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 26 Jul 2025 14:48:09 +0000 Subject: [PATCH 3/4] Update README files with generate-schema command documentation - Add generate-schema command to subcommands list in both English and Spanish READMEs - Add comprehensive documentation section for generate-schema command with usage examples - Include command options and use cases for the new functionality - Fix markdown linting issue with missing language specification for code blocks - Provide Spanish translation of all new documentation content --- README.es.md | 34 +++++++++++++++++++++++++++++++++- README.md | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/README.es.md b/README.es.md index 492857c..105417f 100644 --- a/README.es.md +++ b/README.es.md @@ -102,6 +102,7 @@ struct structure.yaml . Ejecuta el script con el siguiente comando usando uno de los siguientes subcomandos: - `generate`: Genera la estructura del proyecto basada en la configuración YAML. +- `generate-schema`: Genera esquema JSON para las plantillas de estructura disponibles. - `validate`: Valida la configuración YAML para asegurarte de que sea válida. - `info`: Muestra información sobre el script y sus dependencias. - `list`: Lista las estructuras disponibles. @@ -133,6 +134,37 @@ struct generate \ ``` +### Comando Generate Schema + +El comando `generate-schema` crea definiciones de esquema JSON para las plantillas de estructura disponibles, facilitando que las herramientas e IDEs proporcionen autocompletado y validación. + +#### Uso Básico + +```sh +# Generar esquema a stdout +struct generate-schema + +# Generar esquema con ruta de estructuras personalizada +struct generate-schema -s /ruta/a/estructuras/personalizadas + +# Guardar esquema en archivo +struct generate-schema -o schema.json + +# Combinar ruta personalizada y archivo de salida +struct generate-schema -s /ruta/a/estructuras/personalizadas -o schema.json +``` + +#### Opciones del Comando + +- `-s, --structures-path`: Ruta a definiciones de estructura adicionales (opcional) +- `-o, --output`: Ruta del archivo de salida para el esquema (predeterminado: stdout) + +El esquema generado incluye todas las estructuras disponibles tanto del directorio contribs integrado como de cualquier ruta de estructuras personalizada que especifiques. Esto es útil para: + +- Autocompletado IDE al escribir archivos `.struct.yaml` +- Validación de referencias de estructura en tus configuraciones +- Descubrimiento programático de plantillas disponibles + ## 📄 Configuración YAML Aquí tienes un ejemplo de un archivo de configuración YAML: @@ -402,7 +434,7 @@ Puedes referenciar valores del mapping en tus plantillas usando la variable `map Esto se renderizará como: -``` +```text 987654321 ``` diff --git a/README.md b/README.md index 88bc903..be1984e 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ struct generate structure.yaml . Run the script with the following command using one of the following subcommands: - `generate`: Generate the project structure based on the YAML configuration. +- `generate-schema`: Generate JSON schema for available structure templates. - `validate`: Validate the YAML configuration file. - `info`: Display information about the script and its dependencies. - `list`: List the available structs @@ -128,6 +129,37 @@ struct generate \ ./my-terraform-module ``` +### Generate Schema Command + +The `generate-schema` command creates JSON schema definitions for available structure templates, making it easier for tools and IDEs to provide autocompletion and validation. + +#### Basic Usage + +```sh +# Generate schema to stdout +struct generate-schema + +# Generate schema with custom structures path +struct generate-schema -s /path/to/custom/structures + +# Save schema to file +struct generate-schema -o schema.json + +# Combine custom path and output file +struct generate-schema -s /path/to/custom/structures -o schema.json +``` + +#### Command Options + +- `-s, --structures-path`: Path to additional structure definitions (optional) +- `-o, --output`: Output file path for the schema (default: stdout) + +The generated schema includes all available structures from both the built-in contribs directory and any custom structures path you specify. This is useful for: + +- IDE autocompletion when writing `.struct.yaml` files +- Validation of structure references in your configurations +- Programmatic discovery of available templates + ## 📝 YAML Configuration ### Configuration Properties @@ -471,7 +503,7 @@ You can reference mapping values in your templates using the `mappings` variable This will render as: -``` +```text 987654321 ``` From 011c5704700c8ea7887e75194f772e407b218cb9 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Sat, 26 Jul 2025 14:58:00 +0000 Subject: [PATCH 4/4] Fix pytest tests for generate-schema command - Fix comprehensive mock setup for os.path functions to avoid NoneType errors - Implement proper mock_join_side_effect to handle path construction correctly - Update mock_relpath_side_effect to return proper file paths with extensions - Correct mock_exists_side_effect logic for file output directory creation tests - Ensure all tests properly simulate filesystem behavior with complete mocking - All 8 generate_schema tests now passing successfully Test coverage includes: - Command initialization and argument parsing - stdout output with multiple structures and subdirectories - file output with directory creation - custom structures path handling - nonexistent path graceful handling - empty directory handling --- tests/test_commands.py | 130 ++++++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 34 deletions(-) diff --git a/tests/test_commands.py b/tests/test_commands.py index e0360e2..b40dc20 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -117,17 +117,35 @@ def test_generate_schema_stdout_output(parser): ('/path/to/contribs/subdir', [], ['nested-struct.yaml']) ] + def mock_join_side_effect(*args): + if len(args) == 2 and args[0] == '/path/to/commands' and args[1] == '..': + return '/path/to' + elif len(args) == 3 and args[0] == '/path/to' and args[1] == '..' and args[2] == 'contribs': + return '/path/to/contribs' + elif len(args) == 2: + return '/'.join(args) + else: + return '/'.join(args) + + def mock_relpath_side_effect(file_path, base_path): + if file_path == '/path/to/contribs/terraform-module.yaml': + return 'terraform-module.yaml' + elif file_path == '/path/to/contribs/docker-files.yaml': + return 'docker-files.yaml' + elif file_path == '/path/to/contribs/subdir/nested-struct.yaml': + return 'subdir/nested-struct.yaml' + return 'unknown.yaml' + with patch('os.path.dirname') as mock_dirname, \ - patch('os.path.realpath') as mock_realpath, \ - patch('os.path.join') as mock_join, \ - patch('os.path.exists', return_value=True), \ - patch('os.walk', return_value=mock_walk_data), \ - patch('os.path.relpath', side_effect=['terraform-module', 'docker-files', 'subdir/nested-struct']), \ - patch('builtins.print') as mock_print: + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join', side_effect=mock_join_side_effect), \ + patch('os.path.exists', return_value=True), \ + patch('os.walk', return_value=mock_walk_data), \ + patch('os.path.relpath', side_effect=mock_relpath_side_effect), \ + patch('builtins.print') as mock_print: mock_dirname.return_value = '/path/to/commands' mock_realpath.return_value = '/path/to/commands' - mock_join.return_value = '/path/to/contribs' command._generate_schema(args) @@ -162,19 +180,39 @@ def test_generate_schema_file_output(parser): mock_file = MagicMock() + def mock_join_side_effect(*args): + if len(args) == 2 and args[0] == '/path/to/commands' and args[1] == '..': + return '/path/to' + elif len(args) == 3 and args[0] == '/path/to' and args[1] == '..' and args[2] == 'contribs': + return '/path/to/contribs' + elif len(args) == 2: + return '/'.join(args) + else: + return '/'.join(args) + + def mock_relpath_side_effect(file_path, base_path): + if file_path == '/path/to/contribs/test-struct.yaml': + return 'test-struct.yaml' + return 'unknown.yaml' + + def mock_exists_side_effect(path): + # Return False for output directory so makedirs gets called + if path == '/output': + return False + return True + with patch('os.path.dirname') as mock_dirname, \ - patch('os.path.realpath') as mock_realpath, \ - patch('os.path.join') as mock_join, \ - patch('os.path.exists', return_value=True), \ - patch('os.walk', return_value=mock_walk_data), \ - patch('os.path.relpath', return_value='test-struct'), \ - patch('os.makedirs') as mock_makedirs, \ - patch('builtins.open', return_value=mock_file) as mock_open, \ - patch('builtins.print') as mock_print: + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join', side_effect=mock_join_side_effect), \ + patch('os.path.exists', side_effect=mock_exists_side_effect), \ + patch('os.walk', return_value=mock_walk_data), \ + patch('os.path.relpath', side_effect=mock_relpath_side_effect), \ + patch('os.makedirs') as mock_makedirs, \ + patch('builtins.open', return_value=mock_file) as mock_open, \ + patch('builtins.print') as mock_print: mock_dirname.side_effect = ['/path/to/commands', '/output'] mock_realpath.return_value = '/path/to/commands' - mock_join.return_value = '/path/to/contribs' mock_file.__enter__.return_value = mock_file command._generate_schema(args) @@ -210,23 +248,33 @@ def mock_walk_side_effect(path): else: # contribs path return [('/path/to/contribs', [], ['builtin-struct.yaml'])] - def mock_relpath_side_effect(file_path, base_path): - if 'custom-struct' in file_path: - return 'custom-struct' + def mock_join_side_effect(*args): + if len(args) == 2 and args[0] == '/path/to/commands' and args[1] == '..': + return '/path/to' + elif len(args) == 3 and args[0] == '/path/to' and args[1] == '..' and args[2] == 'contribs': + return '/path/to/contribs' + elif len(args) == 2: + return '/'.join(args) else: - return 'builtin-struct' + return '/'.join(args) + + def mock_relpath_side_effect(file_path, base_path): + if file_path == '/custom/structures/custom-struct.yaml': + return 'custom-struct.yaml' + elif file_path == '/path/to/contribs/builtin-struct.yaml': + return 'builtin-struct.yaml' + return 'unknown.yaml' with patch('os.path.dirname') as mock_dirname, \ - patch('os.path.realpath') as mock_realpath, \ - patch('os.path.join') as mock_join, \ - patch('os.path.exists', return_value=True), \ - patch('os.walk', side_effect=mock_walk_side_effect), \ - patch('os.path.relpath', side_effect=mock_relpath_side_effect), \ - patch('builtins.print') as mock_print: + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join', side_effect=mock_join_side_effect), \ + patch('os.path.exists', return_value=True), \ + patch('os.walk', side_effect=mock_walk_side_effect), \ + patch('os.path.relpath', side_effect=mock_relpath_side_effect), \ + patch('builtins.print') as mock_print: mock_dirname.return_value = '/path/to/commands' mock_realpath.return_value = '/path/to/commands' - mock_join.return_value = '/path/to/contribs' command._generate_schema(args) @@ -315,20 +363,34 @@ def mock_walk_side_effect(path): else: # contribs path return [('/path/to/contribs', [], ['builtin.yaml'])] + def mock_join_side_effect(*args): + if len(args) == 2 and args[0] == '/path/to/commands' and args[1] == '..': + return '/path/to' + elif len(args) == 3 and args[0] == '/path/to' and args[1] == '..' and args[2] == 'contribs': + return '/path/to/contribs' + elif len(args) == 2: + return '/'.join(args) + else: + return '/'.join(args) + def mock_exists_side_effect(path): return '/nonexistent/path' not in path + def mock_relpath_side_effect(file_path, base_path): + if file_path == '/path/to/contribs/builtin.yaml': + return 'builtin.yaml' + return 'unknown.yaml' + with patch('os.path.dirname') as mock_dirname, \ - patch('os.path.realpath') as mock_realpath, \ - patch('os.path.join') as mock_join, \ - patch('os.path.exists', side_effect=mock_exists_side_effect), \ - patch('os.walk', side_effect=mock_walk_side_effect), \ - patch('os.path.relpath', return_value='builtin'), \ - patch('builtins.print') as mock_print: + patch('os.path.realpath') as mock_realpath, \ + patch('os.path.join', side_effect=mock_join_side_effect), \ + patch('os.path.exists', side_effect=mock_exists_side_effect), \ + patch('os.walk', side_effect=mock_walk_side_effect), \ + patch('os.path.relpath', side_effect=mock_relpath_side_effect), \ + patch('builtins.print') as mock_print: mock_dirname.return_value = '/path/to/commands' mock_realpath.return_value = '/path/to/commands' - mock_join.return_value = '/path/to/contribs' command._generate_schema(args)