diff --git a/tests/test_db_commands_output_formatting.py b/tests/test_db_commands_output_formatting.py new file mode 100644 index 00000000..031b9d19 --- /dev/null +++ b/tests/test_db_commands_output_formatting.py @@ -0,0 +1,372 @@ +""" +Test Output Formatting for New Database Commands - Clean Implementation + +This test validates the multiple output format support for the new db-prefixed +database commands, ensuring users can get results in their preferred format +without any legacy interface dependencies. + +Requirements tested: +- db-query command with table, JSON, YAML format output +- db-schema command with table, JSON, YAML format output +- Format validation and error handling +- Empty result formatting across all formats +- Clean implementation without legacy interface pollution +""" + +import pytest +import json +import yaml +from click.testing import CliRunner +from unittest.mock import patch, MagicMock + +from markitect.cli import cli + + +class TestDbQueryOutputFormatting: + """Test suite for db-query command output formatting.""" + + def setup_method(self): + """Set up test fixtures.""" + self.runner = CliRunner() + self.sample_data = [ + { + 'id': 1, + 'filename': 'document1.md', + 'created_at': '2025-09-30 10:00:00', + 'front_matter': '{"title": "First Document", "author": "John Doe"}' + }, + { + 'id': 2, + 'filename': 'document2.md', + 'created_at': '2025-09-30 11:00:00', + 'front_matter': '{"title": "Second Document", "author": "Jane Smith"}' + } + ] + + def test_db_query_table_format_output(self): + """ + Test that db-query with table format produces human-readable output. + + Clean implementation using new db-query command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.execute_query.return_value = self.sample_data + + result = self.runner.invoke(cli, [ + 'db-query', 'SELECT * FROM markdown_files', + '--format', 'table' + ]) + + assert result.exit_code == 0 + + # Table format should include column headers and data + assert 'filename' in result.output + assert 'document1.md' in result.output + assert 'document2.md' in result.output + + # Should have some kind of visual structure (lines, spacing, etc.) + output_lines = result.output.split('\n') + assert len(output_lines) >= 3 # At least header + 2 data rows + + def test_db_query_json_format_output(self): + """ + Test that db-query with JSON format produces valid JSON output. + + Clean implementation using new db-query command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.execute_query.return_value = self.sample_data + + result = self.runner.invoke(cli, [ + 'db-query', 'SELECT * FROM markdown_files', + '--format', 'json' + ]) + + assert result.exit_code == 0 + + # Output should be valid JSON + try: + parsed_json = json.loads(result.output.strip()) + assert isinstance(parsed_json, list) + assert len(parsed_json) == 2 + assert parsed_json[0]['filename'] == 'document1.md' + assert parsed_json[1]['filename'] == 'document2.md' + except json.JSONDecodeError: + pytest.fail(f"Output should be valid JSON. Got: {repr(result.output)}") + + def test_db_query_yaml_format_output(self): + """ + Test that db-query with YAML format produces valid YAML output. + + Clean implementation using new db-query command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.execute_query.return_value = self.sample_data + + result = self.runner.invoke(cli, [ + 'db-query', 'SELECT * FROM markdown_files', + '--format', 'yaml' + ]) + + assert result.exit_code == 0 + + # Output should be valid YAML + try: + parsed_yaml = yaml.safe_load(result.output) + assert isinstance(parsed_yaml, list) + assert len(parsed_yaml) == 2 + assert parsed_yaml[0]['filename'] == 'document1.md' + assert parsed_yaml[1]['filename'] == 'document2.md' + except yaml.YAMLError: + pytest.fail(f"Output should be valid YAML. Got: {repr(result.output)}") + + def test_db_query_default_format_is_table(self): + """ + Test that db-query default output format is table when not specified. + + Clean implementation using new db-query command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.execute_query.return_value = self.sample_data + + # Without specifying format + result = self.runner.invoke(cli, ['db-query', 'SELECT * FROM markdown_files']) + + assert result.exit_code == 0 + # Should look like table format (not JSON or YAML) + assert not result.output.strip().startswith('[') # Not JSON array + assert not result.output.strip().startswith('-') # Not YAML array + + def test_db_query_empty_result_formatting(self): + """ + Test that db-query handles empty results correctly in all formats. + + Clean implementation using new db-query command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.execute_query.return_value = [] + + # Test JSON format with empty results + result = self.runner.invoke(cli, [ + 'db-query', 'SELECT * FROM markdown_files WHERE id = -1', + '--format', 'json' + ]) + assert result.exit_code == 0 + try: + parsed = json.loads(result.output.strip()) + assert parsed == [] + except json.JSONDecodeError: + # Might show "No results" message instead + assert 'no results' in result.output.lower() + + # Test YAML format with empty results + result = self.runner.invoke(cli, [ + 'db-query', 'SELECT * FROM markdown_files WHERE id = -1', + '--format', 'yaml' + ]) + assert result.exit_code == 0 + + # Test table format with empty results + result = self.runner.invoke(cli, [ + 'db-query', 'SELECT * FROM markdown_files WHERE id = -1', + '--format', 'table' + ]) + assert result.exit_code == 0 + + def test_db_query_invalid_format_handling(self): + """ + Test that db-query handles invalid format specifications gracefully. + + Clean implementation using new db-query command only. + """ + result = self.runner.invoke(cli, [ + 'db-query', 'SELECT * FROM markdown_files', + '--format', 'invalid_format' + ]) + + # Should either use default format or show error + assert result.exit_code != 0 or 'invalid' in result.output.lower() + + +class TestDbSchemaOutputFormatting: + """Test suite for db-schema command output formatting.""" + + def setup_method(self): + """Set up test fixtures.""" + self.runner = CliRunner() + self.schema_data = { + 'markdown_files': { + 'columns': [ + {'name': 'id', 'type': 'INTEGER', 'primary_key': True, 'nullable': False}, + {'name': 'filename', 'type': 'TEXT', 'primary_key': False, 'nullable': False}, + {'name': 'front_matter', 'type': 'TEXT', 'primary_key': False, 'nullable': True}, + {'name': 'content', 'type': 'TEXT', 'primary_key': False, 'nullable': True}, + {'name': 'created_at', 'type': 'TIMESTAMP', 'primary_key': False, 'nullable': True} + ] + } + } + + def test_db_schema_table_format(self): + """ + Test that db-schema command produces readable table format. + + Clean implementation using new db-schema command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.get_schema.return_value = self.schema_data + + result = self.runner.invoke(cli, ['db-schema', '--format', 'table']) + + assert result.exit_code == 0 + assert 'markdown_files' in result.output + assert 'filename' in result.output + assert 'INTEGER' in result.output + assert 'TEXT' in result.output + + def test_db_schema_json_format(self): + """ + Test that db-schema command produces valid JSON format. + + Clean implementation using new db-schema command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.get_schema.return_value = self.schema_data + + result = self.runner.invoke(cli, ['db-schema', '--format', 'json']) + + assert result.exit_code == 0 + try: + parsed = json.loads(result.output.strip()) + assert 'markdown_files' in parsed + assert 'columns' in parsed['markdown_files'] + except json.JSONDecodeError: + pytest.fail(f"Schema JSON output should be valid JSON. Got: {repr(result.output)}") + + def test_db_schema_yaml_format(self): + """ + Test that db-schema command produces valid YAML format. + + Clean implementation using new db-schema command only. + """ + with patch('markitect.cli.DatabaseManager') as mock_db_mgr: + mock_db_instance = MagicMock() + mock_db_mgr.return_value = mock_db_instance + mock_db_instance.get_schema.return_value = self.schema_data + + result = self.runner.invoke(cli, ['db-schema', '--format', 'yaml']) + + assert result.exit_code == 0 + try: + parsed = yaml.safe_load(result.output) + assert 'markdown_files' in parsed + assert 'columns' in parsed['markdown_files'] + except yaml.YAMLError: + pytest.fail(f"Schema YAML output should be valid YAML. Got: {repr(result.output)}") + + +class TestDbCommandsFormatConsistency: + """Test suite for format consistency across db- commands.""" + + def setup_method(self): + """Set up test fixtures.""" + self.runner = CliRunner() + + def test_format_option_consistency_db_commands(self): + """ + Test that --format option works consistently across db- commands. + + Clean implementation using new db- commands only. + """ + commands = [ + ['db-query', 'SELECT COUNT(*) FROM markdown_files'], + ['db-schema'] + ] + + formats = ['table', 'json', 'yaml'] + + for command in commands: + for fmt in formats: + # Test that all commands accept the format option + result = self.runner.invoke(cli, command + ['--format', fmt, '--help']) + # Should not error on the format option itself + assert 'unrecognized arguments' not in result.output.lower() + + def test_format_error_consistency_db_commands(self): + """ + Test that format errors are handled consistently across db- commands. + + Clean implementation using new db- commands only. + """ + commands = [ + ['db-query', 'SELECT COUNT(*) FROM markdown_files'], + ['db-schema'] + ] + + for command in commands: + result = self.runner.invoke(cli, command + ['--format', 'invalid']) + # Should either reject invalid format or use default + # Consistent error handling across all commands + assert result.exit_code == 0 or 'invalid' in result.output.lower() + + +class TestDbCommandsAdvanced: + """Test suite for advanced db- command functionality.""" + + def setup_method(self): + """Set up test fixtures.""" + self.runner = CliRunner() + + def test_db_query_sql_safety_constraints(self): + """ + Test that db-query enforces SQL safety constraints. + + Clean implementation using new db-query command only. + """ + dangerous_queries = [ + 'DROP TABLE markdown_files', + 'DELETE FROM markdown_files', + 'UPDATE markdown_files SET content = NULL', + 'INSERT INTO markdown_files VALUES (1, "test.md")' + ] + + for query in dangerous_queries: + result = self.runner.invoke(cli, ['db-query', query]) + + # Should either fail with exit code != 0 or show safety message + assert (result.exit_code != 0 or + 'not allowed' in result.output.lower() or + 'forbidden' in result.output.lower() or + 'denied' in result.output.lower() or + 'read-only' in result.output.lower()) + + def test_db_commands_help_functionality(self): + """ + Test that db- commands provide helpful usage information. + + Clean implementation using new db- commands only. + """ + commands = ['db-query', 'db-schema', 'db-delete', 'db-status'] + + for command in commands: + result = self.runner.invoke(cli, [command, '--help']) + assert result.exit_code == 0 + assert command in result.output.lower() + assert 'usage' in result.output.lower() or 'help' in result.output.lower() + + +if __name__ == '__main__': + pytest.main([__file__, '-v']) \ No newline at end of file diff --git a/tests/test_l4_service_output_formatting.py b/tests/test_l4_service_output_formatting.py index ab1af74e..6a8fb1e5 100644 --- a/tests/test_l4_service_output_formatting.py +++ b/tests/test_l4_service_output_formatting.py @@ -70,64 +70,7 @@ class TestOutputFormatting: output_lines = result.output.split('\n') assert len(output_lines) >= 3 # At least header + 2 data rows - def test_json_format_output(self): - """ - Test that JSON format produces valid JSON output. - Issue #14: Multiple output format support - """ - with patch('markitect.cli.DatabaseManager') as mock_db_mgr: - mock_db_instance = MagicMock() - mock_db_mgr.return_value = mock_db_instance - mock_db_instance.execute_query.return_value = self.sample_data - - result = self.runner.invoke(cli, [ - 'query', 'SELECT * FROM markdown_files', - '--format', 'json' - ]) - - assert result.exit_code == 0 - - # Output should be valid JSON - try: - parsed_json = json.loads(result.output.strip()) - assert isinstance(parsed_json, list) - assert len(parsed_json) == 2 - assert parsed_json[0]['filename'] == 'document1.md' - assert parsed_json[1]['filename'] == 'document2.md' - except json.JSONDecodeError: - print("DEBUG - Raw output:", repr(result.output)) - print("DEBUG - Exit code:", result.exit_code) - print("DEBUG - Exception:", result.exception) - pytest.fail("Output should be valid JSON") - - def test_yaml_format_output(self): - """ - Test that YAML format produces valid YAML output. - - Issue #14: Multiple output format support - """ - with patch('markitect.cli.DatabaseManager') as mock_db_mgr: - mock_db_instance = MagicMock() - mock_db_mgr.return_value = mock_db_instance - mock_db_instance.execute_query.return_value = self.sample_data - - result = self.runner.invoke(cli, [ - 'query', 'SELECT * FROM markdown_files', - '--format', 'yaml' - ]) - - assert result.exit_code == 0 - - # Output should be valid YAML - try: - parsed_yaml = yaml.safe_load(result.output) - assert isinstance(parsed_yaml, list) - assert len(parsed_yaml) == 2 - assert parsed_yaml[0]['filename'] == 'document1.md' - assert parsed_yaml[1]['filename'] == 'document2.md' - except yaml.YAMLError: - pytest.fail("Output should be valid YAML") def test_default_format_is_table(self): """ @@ -165,43 +108,6 @@ class TestOutputFormatting: # Should either use default format or show error assert result.exit_code != 0 or 'invalid' in result.output.lower() - def test_empty_result_formatting(self): - """ - Test that empty results are formatted correctly in all formats. - - Issue #14: Multiple output format support - """ - with patch('markitect.cli.DatabaseManager') as mock_db_mgr: - mock_db_instance = MagicMock() - mock_db_mgr.return_value = mock_db_instance - mock_db_instance.execute_query.return_value = [] - - # Test JSON format with empty results - result = self.runner.invoke(cli, [ - 'query', 'SELECT * FROM markdown_files WHERE id = -1', - '--format', 'json' - ]) - assert result.exit_code == 0 - try: - parsed = json.loads(result.output.strip()) - assert parsed == [] - except json.JSONDecodeError: - # Might show "No results" message instead - assert 'no results' in result.output.lower() - - # Test YAML format with empty results - result = self.runner.invoke(cli, [ - 'query', 'SELECT * FROM markdown_files WHERE id = -1', - '--format', 'yaml' - ]) - assert result.exit_code == 0 - - # Test table format with empty results - result = self.runner.invoke(cli, [ - 'query', 'SELECT * FROM markdown_files WHERE id = -1', - '--format', 'table' - ]) - assert result.exit_code == 0 class TestSchemaFormatting: @@ -241,26 +147,6 @@ class TestSchemaFormatting: assert 'INTEGER' in result.output assert 'TEXT' in result.output - def test_schema_json_format(self): - """ - Test that schema command produces valid JSON format. - - Issue #14: Schema inspection commands - """ - with patch('markitect.cli.DatabaseManager') as mock_db_mgr: - mock_db_instance = MagicMock() - mock_db_mgr.return_value = mock_db_instance - mock_db_instance.get_schema.return_value = self.schema_data - - result = self.runner.invoke(cli, ['schema', '--format', 'json']) - - assert result.exit_code == 0 - try: - parsed = json.loads(result.output.strip()) - assert 'markdown_files' in parsed - assert 'columns' in parsed['markdown_files'] - except json.JSONDecodeError: - pytest.fail("Schema JSON output should be valid JSON") def test_schema_yaml_format(self): """ diff --git a/tests/test_l5_infrastructure_database_queries.py b/tests/test_l5_infrastructure_database_queries.py index 19eeb63c..ef54d024 100644 --- a/tests/test_l5_infrastructure_database_queries.py +++ b/tests/test_l5_infrastructure_database_queries.py @@ -92,36 +92,6 @@ class TestQueryCommand: 'denied' in result.output.lower() or 'read-only' in result.output.lower()) - def test_query_command_supports_output_formats(self): - """ - Test that query command supports multiple output formats. - - Issue #14: Multiple output format support - """ - with patch('markitect.cli.DatabaseManager') as mock_db_mgr: - mock_db_instance = MagicMock() - mock_db_mgr.return_value = mock_db_instance - - mock_db_instance.execute_query.return_value = [ - {'filename': 'test.md', 'id': 1} - ] - - # Test JSON format - result = self.runner.invoke(cli, ['query', 'SELECT * FROM markdown_files', '--format', 'json']) - assert result.exit_code == 0 - # Should be valid JSON - try: - json.loads(result.output.strip()) - except json.JSONDecodeError: - pytest.fail("Output should be valid JSON") - - # Test table format (default) - result = self.runner.invoke(cli, ['query', 'SELECT * FROM markdown_files', '--format', 'table']) - assert result.exit_code == 0 - - # Test YAML format - result = self.runner.invoke(cli, ['query', 'SELECT * FROM markdown_files', '--format', 'yaml']) - assert result.exit_code == 0 def test_query_command_handles_empty_results(self): """