Files
markitect-main/tests/test_l5_infrastructure_database_queries.py
tegwick cbf82b74cb feat: Establish CLI subsystem *-stats command naming convention
Implemented comprehensive CLI naming consistency by standardizing all subsystem
commands to use *-stats for status reporting:

## Changes Made:

### 1. Removed Unnecessary Skipped Tests
- Removed two deferred tests for global option path display from Issue #39
- Tests were marked as requiring "complex CLI changes" and deemed not worth effort
- Cleaner test suite without placeholder functionality

### 2. Renamed cache-info → cache-stats
- Updated CLI command: @cli.command('cache-stats')
- Updated function name: cache_info() → cache_stats()
- Updated all test files to use cache-stats
- Consistent with subsystem naming convention

### 3. Renamed db-status → db-stats
- Updated CLI command: @cli.command('db-stats')
- Updated function name: db_status() → db_stats()
- Updated all test files and references to use db-stats
- Maintains database subsystem consistency

### 4. Implemented config-stats Command
- New CLI command following *-stats convention
- Displays configuration statistics and status information
- Supports all output formats: table, json, yaml, simple
- Integrates with existing config system when available
- Provides fallback functionality for basic configuration reporting

## Established Convention:
All CLI subsystems now have consistent *-stats commands:
-  ast-stats (already existed)
-  cache-stats (renamed from cache-info)
-  db-stats (renamed from db-status)
-  config-stats (newly implemented)

## Benefits:
- Intuitive command discovery (users know to try *-stats for any subsystem)
- Consistent CLI experience across all subsystems
- Better organized help documentation
- Professional CLI interface following standard conventions

All tests updated and passing. CLI maintains backward compatibility for
essential functionality while establishing clear, consistent patterns.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 22:13:07 +02:00

397 lines
14 KiB
Python

"""
Test Database Query CLI Commands - Issue #14
This test validates the implementation of database query CLI commands for
delivering the core USP "Relational Document Metadata" through queryable
database interface.
Requirements tested:
- markitect query <sql> command with safety constraints
- markitect schema command for database structure inspection
- markitect db-data <file> command for file metadata display (updated from metadata in Issue #38)
- Multiple output format support (table, JSON, YAML)
- Read-only access and SQL injection protection
- Integration with existing DatabaseManager
"""
import pytest
import tempfile
import os
import json
from pathlib import Path
from click.testing import CliRunner
from unittest.mock import patch, MagicMock
# Import the CLI module (will be extended during implementation)
try:
from markitect.cli import cli, query_command, schema_command, db_data_command
except ImportError:
# Commands don't exist yet - this is expected in TDD
from markitect.cli import cli
class TestQueryCommand:
"""Test suite for markitect query command."""
def setup_method(self):
"""Set up test fixtures."""
self.runner = CliRunner()
def test_query_command_exists(self):
"""
Test that the query command is accessible.
Issue #14: SQL query interface with safety constraints
"""
# Test that the main query command exists and is callable
result = self.runner.invoke(cli, ['query', '--help'])
assert result.exit_code == 0
assert 'query' in result.output.lower()
assert 'execute sql query' in result.output.lower() or 'sql' in result.output.lower()
def test_query_command_executes_select(self):
"""
Test that query command can execute SELECT statements.
Issue #14: SQL query interface with safety constraints
"""
with patch('markitect.cli.DatabaseManager') as mock_db_mgr:
mock_db_instance = MagicMock()
mock_db_mgr.return_value = mock_db_instance
# Mock query result
mock_db_instance.execute_query.return_value = [
{'id': 1, 'filename': 'test.md', 'created_at': '2025-09-25'}
]
result = self.runner.invoke(cli, ['query', 'SELECT * FROM markdown_files LIMIT 1'])
assert result.exit_code == 0
assert 'test.md' in result.output
def test_database_query_command_prevents_dangerous_write_operations(self):
"""
Test that query command blocks dangerous SQL operations.
Issue #14: SQL query interface with safety constraints
"""
dangerous_queries = [
'DROP TABLE markdown_files',
'DELETE FROM markdown_files',
'UPDATE markdown_files SET filename = "hacked"',
'INSERT INTO markdown_files VALUES (1, "hack")',
'CREATE TABLE hack (id INT)',
'ALTER TABLE markdown_files ADD COLUMN hack TEXT'
]
for dangerous_sql in dangerous_queries:
result = self.runner.invoke(cli, ['query', dangerous_sql])
assert result.exit_code != 0
assert ('not allowed' in result.output.lower() or
'allowed' in result.output.lower() or
'denied' in result.output.lower() or
'read-only' in result.output.lower())
def test_query_command_handles_empty_results(self):
"""
Test that query command handles empty query results gracefully.
Issue #14: SQL query interface with safety constraints
"""
with patch('markitect.cli.DatabaseManager') as mock_db_mgr:
mock_db_instance = MagicMock()
mock_db_mgr.return_value = mock_db_instance
# Mock empty result
mock_db_instance.execute_query.return_value = []
result = self.runner.invoke(cli, ['query', 'SELECT * FROM markdown_files WHERE id = -1'])
assert result.exit_code == 0
assert 'no results' in result.output.lower() or len(result.output.strip()) == 0
def test_query_command_handles_invalid_sql(self):
"""
Test that query command handles invalid SQL gracefully.
Issue #14: SQL query interface with safety constraints
"""
with patch('markitect.cli.DatabaseManager') as mock_db_mgr:
mock_db_instance = MagicMock()
mock_db_mgr.return_value = mock_db_instance
# Mock SQL error
mock_db_instance.execute_query.side_effect = Exception("SQL syntax error")
result = self.runner.invoke(cli, ['query', 'SELECT * FROM nonexistent_table'])
assert result.exit_code != 0
assert 'error' in result.output.lower()
class TestSchemaCommand:
"""Test suite for markitect schema command."""
def setup_method(self):
"""Set up test fixtures."""
self.runner = CliRunner()
def test_schema_command_exists(self):
"""
Test that the schema command is accessible.
Issue #14: Schema inspection commands
"""
result = self.runner.invoke(cli, ['schema', '--help'])
assert result.exit_code == 0
assert 'schema' in result.output.lower()
assert ('database' in result.output.lower() or
'table' in result.output.lower() or
'structure' in result.output.lower())
def test_schema_command_shows_database_structure(self):
"""
Test that schema command displays database structure.
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 schema information
mock_db_instance.get_schema.return_value = {
'markdown_files': {
'columns': [
{'name': 'id', 'type': 'INTEGER', 'primary_key': True},
{'name': 'filename', 'type': 'TEXT', 'primary_key': False},
{'name': 'front_matter', 'type': 'TEXT', 'primary_key': False},
{'name': 'content', 'type': 'TEXT', 'primary_key': False},
{'name': 'created_at', 'type': 'TIMESTAMP', 'primary_key': False}
]
}
}
result = self.runner.invoke(cli, ['schema'])
assert result.exit_code == 0
assert 'markdown_files' in result.output
assert 'filename' in result.output
assert 'front_matter' in result.output
def test_schema_command_supports_output_formats(self):
"""
Test that schema 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_schema = {
'markdown_files': {
'columns': [{'name': 'id', 'type': 'INTEGER', 'primary_key': True}]
}
}
mock_db_instance.get_schema.return_value = mock_schema
# Test JSON format
result = self.runner.invoke(cli, ['schema', '--format', 'json'])
assert result.exit_code == 0
# Test YAML format
result = self.runner.invoke(cli, ['schema', '--format', 'yaml'])
assert result.exit_code == 0
class TestDbDataCommand:
"""Test suite for markitect db-data command."""
def setup_method(self):
"""Set up test fixtures."""
self.runner = CliRunner()
def test_db_data_command_exists(self):
"""
Test that the db-data command is accessible.
Issue #14: Metadata display functionality (updated for Issue #38)
"""
result = self.runner.invoke(cli, ['db-data', '--help'])
assert result.exit_code == 0
assert 'db-data' in result.output.lower()
assert 'file' in result.output.lower()
def test_db_data_command_displays_file_info(self):
"""
Test that db-data command displays file metadata and front matter.
Issue #14: Metadata display functionality (updated for Issue #38)
"""
with patch('markitect.cli.DatabaseManager') as mock_db_mgr:
mock_db_instance = MagicMock()
mock_db_mgr.return_value = mock_db_instance
# Mock file metadata
mock_db_instance.get_markdown_file.return_value = {
'id': 1,
'filename': 'test.md',
'front_matter': '{"title": "Test Document", "author": "Test Author"}',
'content': '# Test\nContent here',
'created_at': '2025-09-25 12:00:00'
}
result = self.runner.invoke(cli, ['db-data', 'test.md'])
assert result.exit_code == 0
assert 'test.md' in result.output
assert 'Test Document' in result.output
assert 'Test Author' in result.output
def test_db_data_command_handles_missing_file(self):
"""
Test that db-data command handles missing files gracefully.
Issue #14: Metadata display functionality (updated for Issue #38)
"""
with patch('markitect.cli.DatabaseManager') as mock_db_mgr:
mock_db_instance = MagicMock()
mock_db_mgr.return_value = mock_db_instance
# Mock file not found
mock_db_instance.get_markdown_file.return_value = None
result = self.runner.invoke(cli, ['db-data', 'nonexistent.md'])
assert result.exit_code != 0
assert 'not found' in result.output.lower()
def test_db_data_command_supports_output_formats(self):
"""
Test that db-data command supports multiple output formats.
Issue #14: Multiple output format support (updated for Issue #38)
"""
with patch('markitect.cli.DatabaseManager') as mock_db_mgr:
mock_db_instance = MagicMock()
mock_db_mgr.return_value = mock_db_instance
mock_metadata = {
'filename': 'test.md',
'front_matter': '{"title": "Test"}',
'created_at': '2025-09-25'
}
mock_db_instance.get_markdown_file.return_value = mock_metadata
# Test JSON format
result = self.runner.invoke(cli, ['db-data', 'test.md', '--format', 'json'])
assert result.exit_code == 0
# Test YAML format
result = self.runner.invoke(cli, ['db-data', 'test.md', '--format', 'yaml'])
assert result.exit_code == 0
class TestDatabaseIntegration:
"""Test suite for database integration functionality."""
def test_database_manager_query_method(self):
"""
Test that DatabaseManager supports query execution.
Issue #14: Integration with existing DatabaseManager functionality
"""
# This test ensures the DatabaseManager has or will have query capabilities
from markitect.database import DatabaseManager
# The DatabaseManager should have a method for executing queries
db_manager = DatabaseManager(':memory:')
db_manager.initialize_database()
# This method will be implemented as part of Issue #14
assert hasattr(db_manager, 'execute_query') or hasattr(db_manager, 'query')
def test_database_manager_schema_inspection(self):
"""
Test that DatabaseManager supports schema inspection.
Issue #14: Schema inspection commands
"""
from markitect.database import DatabaseManager
db_manager = DatabaseManager(':memory:')
db_manager.initialize_database()
# The DatabaseManager should have a method for getting schema info
assert hasattr(db_manager, 'get_schema') or hasattr(db_manager, 'describe_schema')
class TestQuerySafety:
"""Test suite for SQL query safety and security."""
def test_database_query_enforces_read_only_access_restrictions(self):
"""
Test that only read operations are allowed.
Issue #14: SQL query interface with safety constraints
"""
write_operations = [
'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER', 'TRUNCATE'
]
runner = CliRunner()
for operation in write_operations:
query = f"{operation} markdown_files"
result = runner.invoke(cli, ['query', query])
# Should be rejected
assert result.exit_code != 0 or 'not allowed' in result.output.lower()
class TestQueryTemplates:
"""Test suite for query templates and examples."""
def test_common_query_templates_available(self):
"""
Test that common query templates are available.
Issue #14: Query templates and examples
"""
runner = CliRunner()
# Test that templates or examples are shown in help
result = runner.invoke(cli, ['query', '--help'])
assert result.exit_code == 0
# Should mention examples or templates
assert ('example' in result.output.lower() or
'template' in result.output.lower() or
'SELECT' in result.output)
def test_template_execution(self):
"""
Test that query templates can be executed.
Issue #14: Query templates and examples
"""
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 = []
runner = CliRunner()
# Test common templates that should work
common_queries = [
'SELECT COUNT(*) FROM markdown_files',
'SELECT filename FROM markdown_files',
'SELECT * FROM markdown_files ORDER BY created_at DESC'
]
for query in common_queries:
result = runner.invoke(cli, ['query', query])
assert result.exit_code == 0